Sunday, March 21, 2010

How to Set or Increase the -Xmx Heap Memory of An Executable Java Jar

So a lot of people say that setting the -Xmx heap memory of an executable Java jar cannot be done, that it must be done on the command line or via batch script negating the double click functionality of a jar or Java Webstart.

Actually it can be achieved quite easily with a mostly cross platform compatible work around/solution (although this fix won't help you if you have a custom Java security policy - 99% of people don't ;))

The benefit of the solution below is that it doesn't rely on hard coded paths which will break cross platform compatibility:
  1. Create a small separate runner class with (another) main method in the jar. Set the main-class attribute of the jar to use this class.
  2. The new main method should check your conditions (e.g. you have enough memory).
  3. The new main method should start a new Java process without a hardcoded path for cross platform compatibility if the conditions are not met.
  4. Specify your parameters on the command line for that process (e.g -Xmx)
  5. Do NOT have the new main method use the "-jar" parameter as this does not work on all platforms due to path to jar issues (in fact it only seems to work on Windows).
Basically derive some code from the following example (taken from Image to ZX Spec 1.2). Note this fix uses a non standard option for MAXIMUM HEAP.

Note I am relaxing my usual GPL 2 licence for this Java class, if you derive from it consider it to be BSD licenced. I also ask you put a link or note in your software to my website in your licence/list of licences. The website you should use for reference is http://www.silentsoftware.co.uk

Taken from Image to ZX Spec 1.2.1, where the class "uk.co.silentsoftware.ui.ImageToZxSpec" is the real main class to start the application.

/* Image to ZX Spec
* Copyright (C) 2010 Silent Software (Benjamin Brown)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
* - Redistributions of source code must retain the above
* copyright notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of Silent Software, Silent Development, Benjamin
* Brown nor the names of its contributors may be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package uk.co.silentsoftware;

import uk.co.silentsoftware.ui.ImageToZxSpec;

/**
* Solution/workaround to allow executable jars
* to change their memory settings as Java cannot
* have its command line properties defined in a
* Jar file. Why Sun never implemented anything like
* this is beyond me.
*/
public class ImageToZxSpecRunner {

  /**
    * Bare minimum heap memory for starting app and allowing
    * for reasonable sized images (note deliberate 1 MB
    * smaller than 512 due to rounding/non accurate free
    * heap calculation). The solution allows JVMs with
    * enough heap already to just start without spawning
    * a new process.
    */
    private final static int MIN_HEAP = 511;

    public static void main(String[] args) throws Exception {

    // Do we have enough memory already (some VMs and later Java 6
    // revisions have bigger default heaps based on total machine memory)?
    float heapSizeMegs = (Runtime.getRuntime().maxMemory()/1024)/1024;

    // Yes so start
    if (heapSizeMegs > MIN_HEAP) {
      ImageToZxSpec.main(args);

    // No so set a large heap. Tut - I did use -server mode here originally
    // which does something similar for heap (i.e. can choose a machine specific
    // maximum) but this has some problems with Java 6 R19 for some reason
    // on my single core machine but worked on Java 6 R13 :( so for now I'll
    // use a naughty non standard -XX:+AggressiveHeap option.
    // NOTE I DO NOT RECOMMEND YOU DO THIS FOR PRODUCTION CODE use -Xmx1024m
    // instead (or whatever memory you need) as another constant and use this
    // in place of "-XX:AggressiveHeap" below. E.g.
    // "private final static int RECOMMENDED_HEAP = 1024;"
    // and
    // ...new ProcessBuilder("java","-Xmx"+RECOMMENDED_HEAP+"m"...
    } else {
      String pathToJar = ImageToZxSpecRunner.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath();
      ProcessBuilder pb = new ProcessBuilder("java","-XX:+AggressiveHeap", "-classpath", pathToJar, "uk.co.silentsoftware.ui.ImageToZxSpec");
      pb.start();
    }
  }
}

If you find this useful (or it's saved your bacon at work!) or want to post a more generic/improved version drop me a comment (and please remember to add the licence as described above or your code will break ;) )!

4 comments:

claudio said...

well so you explain how to change -Xmx for a jar. I have an issue on netbeans developing webapps with JBoss application server : in this case , even if you modify from the ide -Xmx to, say , 1024 , the server keeps crashing with permgen exception .
what do you think?

claudio said...

Huh , and thanks for the good job for the GPS locator , i got it
claudio

Benjamin said...

@Claudio

-XX:MaxPermSize=#m

Most of this stuff is documented on Google so I didn't bother to include advanced JVM settings :)

Thomas G√ľnter said...

First, thanks a lot for your helpful posting. I used your solution to get a simple swing ui running.

Anyway, the solution above fails in case the real application class uses some third party libraries specified in the manifest file. Therefore using the hole classpath of the original java call instead of only the path to the jar is helpful:

String classpath = System.getProperty("java.class.path");
ProcessBuilder pb = new ProcessBuilder("java","-XX:+AggressiveHeap", "-classpath", classpath, "uk.co.silentsoftware.ui.ImageToZxSpec");

Regards,
Tom