17

We ship a Java application whose memory demand can vary quite a lot depending on the size of the data it is processing. If you don't set the max VM (virtual memory) size, quite often the JVM quits with an GC failure on big data.

What we'd like to see, is the JVM requesting more memory, as GC fails to provide enough, until the total available VM is exhausted. e.g., start with 128Mb, and increase geometrically (or some other step) whenever the GC failed.

The JVM ("Java") command line allows explicit setting of max VM sizes (various -Xm* commands), and you'd think that would be designed to be adequate. We try to do this in a .cmd file that we ship with the application. But if you pick any specific number, you get one of two bad behaviors: 1) if your number is small enough to work on most target systems (e.g., 1Gb), it isn't big enough for big data, or 2) if you make it very large, the JVM refuses to run on those systems whose actual VM is smaller than specified.

How does one set up Java to use the available VM when needed, without knowing that number in advance, and without grabbing it all on startup?

Ira Baxter
  • 93,541
  • 22
  • 172
  • 341
  • 1
    Please get your terminology straight: there is no such thing as "available VM" or "actual VM". Presumably you mean RAM. – Michael Borgwardt Jul 20 '09 at 09:33
  • 3
    @Michael: perhaps you are confused by "JVM" and "VM" in the same sentence. JVM refers to the Java Virtual Machine. "VM" refers to the virtual memory provided by the OS: http://en.wikipedia.org/wiki/Virtual_memory I'm interested in the max VM that the OS will allow a process to use, which is usually the same value as the OS configures for total VM for all processes. – Ira Baxter Jul 20 '09 at 09:41
  • Ah, now I get it. It's indeed confusing to have those identical abbreviations next to each other. – Michael Borgwardt Jul 20 '09 at 09:52
  • The limited JVM memory options are a really big flaw in Java. While the better answers here are clever hacks, they are still just hacks. I would love to see more elegant memory management in Java 7. – James McMahon Jul 21 '09 at 12:41
  • we have the same problem here with some apps; most cases it will process small sets of data and we would not like to give it that much memory, and then once in a while it needs much memory since it needs to process large sets. we have no solution so far, we will aim for creating a wrapper that checks the size of data to process and that starts up the java process with corresponding parameters...really just a bad hack solution, but we cant see any other way – JohnSmith Aug 10 '10 at 10:10
  • @JohnSmith - we can't see a better either. It worse in our case: you can't tell how big the VM demand will be by inspecting the data, without in effect executing the program on the data itself. So we just want to let the VM demand grow as needed. Among the least awful answers seen here is the wrapper to looks at the machines' VM size and sets the command line parameters. – Ira Baxter Aug 10 '10 at 14:15

14 Answers14

13

You can also use the option: -XX:+AggressiveHeap

This according to the [documentation][1]:

The -XX:+AggressiveHeap option inspects the machine resources (size of memory and number of processors) and attempts to set various parameters to be optimal for long-running, memory allocation-intensive jobs. It was originally intended for machines with large amounts of memory and a large number of CPUs, but in the J2SE platform, version 1.4.1 and later it has shown itself to be useful even on four processor machines. With this option the throughput collector (-XX:+UseParallelGC) is used along with adaptive sizing (-XX:+UseAdaptiveSizePolicy). The physical memory on the machines must be at least 256MB before AggressiveHeap can be used. The size of the initial heap is calculated based on the size of the physical memory and attempts to make maximal use of the physical memory for the heap (i.e., the algorithms attempt to use heaps nearly as large as the total physical memory).

[1]: http://java.sun.com/docs/hotspot/gc1.4.2/#4.2.2. AggressiveHeap|outline

Sindri Traustason
  • 5,445
  • 6
  • 48
  • 66
  • Wow I have never seen these options before... this is great reading, thanks – Newtopian Jul 20 '09 at 11:32
  • 3
    With this option, the algorithms attempt to use heaps nearly as large as the total physical memory. What happens if the user tries to run some other application? Perhaps the option is useful for server dedicated environments? – zkarthik Jul 20 '09 at 11:35
  • 3
    Any `-XX` option is liable to change (or disappear) without warning. They are not a *public API* of the JVM and possibly you should avoid (if possible) bundling a public product with them – oxbow_lakes Jul 20 '09 at 12:32
  • 3
    Indeed -XX:+AggressiveHeap does not seem to be available in 1.5 and later. – Stephen C Jul 21 '09 at 05:58
  • 1
    @zkarthik I believe this option is really only useful for servers that do pretty much nothing other than run one Java VM. I think physical memory also excludes swap space. – Sindri Traustason Jul 22 '09 at 12:17
  • @Stephen C, I thought so too, but AggressiveHeap seems to be mentioned in 6u18's release notes: http://www.oracle.com/technetwork/java/javase/itanium6u18-137932.html – opyate Feb 10 '11 at 14:22
  • zharthik: To answer your question, in a few words.. I reason the option we refer to will only set maximum heap size, I would not think he use the same value for initial or minimum value of the heap size. In that case, OS which uses virtual memory will swap data from memory to the hard drive back and forth. Eventually, your OS goes down if the total memory needed by all applications is too much and OS has not time nor resources enough to do the swapping. I find it dubious that Java has a notion of "maximum heap size" and enforce this ruling. – Martin Andersson Nov 14 '13 at 09:21
  • The Java 8 SE tools reference for windows continues to mention the AggressiveHeap option. https://docs.oracle.com/javase/8/docs/technotes/tools/windows/java.html – jla Nov 26 '18 at 23:18
  • I think a word to caution here - https://support.oracle.com/knowledge/Middleware/1285324_1.html – hagrawal7777 Sep 10 '22 at 04:05
8

The max VM sizes indeed answer to that need (it sets the max value, but the VM will take only necessary, step by step), but if you need several configurations, besides supplying different "cmd" files, I don't really see a way (though i'll search a bit more)

[edit] How about using a first program/script (or even another java program), which would check the available resources for the system, and then only call your program with the appropriate -Xm, according to what it retrieved from system ? That way it would adapt to machines, even if you don't know them before. Could be an idea...

[second edit] Ok, this has been proposed already by skaffman, my bad.

Community
  • 1
  • 1
Gnoupi
  • 4,715
  • 5
  • 34
  • 50
  • 1
    Not your bad, if you look at the timestamps your answer arrived sooner than his. However, the discussion seems to be going on around Brian's response, also not different than yours. Thanks for chiming in. – Ira Baxter Jul 20 '09 at 09:59
8

We have a small C application that we use for launching all of our Java applications via JNI. This allows us to:

  1. Have a meaningful process name (esp important under windows)
  2. Have our own icons (again, important for Windows)
  3. Dynamically build classpaths (we parse out the contents of the /lib file to auto-include all jars)

For our apps, we just hard code the heap limit, but you could easily dynamically configure max heap size based on available memory.

This sort of little app is actually pretty easy to do (it's one of the easiest things to do with JNI). A good starting point would be the source for the JDK (there's a sub-folder for java.exe itself that you can use - that's what we did). Most folks are quite surprised to find that java.exe is a little tiny application (< 200 lines of code) that just invokes JNI and passes command line arguments in (heck, even the use of a method called main() is pretty optional once you start launching things yourself).

Here's code that not only starts up the JVM, etc... but also determines the maximum heap space based on available RAM of the computer. This is a lot of code for an SO post, and it's not at all pretty - but this is battle hardened code - it's been used for almost a decade over many hundreds of installs, etc... Enjoy :

#include <windows.h>
#include <jni.h>
#include <string>
#include <sstream>
using namespace std;

#define STARTUP_CLASS "some/path/to/YourStartupClass"

void vShowError(string sErrorMessage);
void vShowJREError(string sErrorMessage);
void vShowLastError(string sErrorMessage);
void vDestroyVM(JNIEnv *env, JavaVM *jvm);
void vAddOption(string& sName);
string GetClassPath(string root);
string GetJREPath();
int getMaxHeapAvailable(int permGenMB, int maxHeapMB);

JavaVMOption* vm_options;
int mctOptions = 0;
int mctOptionCapacity = 0;


boolean GetApplicationHome(char *buf, jint sz);


typedef jint (CALLBACK *CreateJavaVM)(JavaVM
**pvm, JNIEnv **penv, void *args);

boolean PathExists(string &path)
{
    DWORD dwAttr = GetFileAttributes(path.c_str());
    if (dwAttr == 0xffffffff)
        return FALSE;
    else 
        return TRUE;
}

// returns TRUE is there was an exception, FALSE otherwise
BOOL GetExceptionString(JNIEnv* jenv, string &result)
{
    jthrowable ex;


    if (NULL != (ex = jenv->ExceptionOccurred())) {
        // clear exception 
        jenv->ExceptionClear();

        jmethodID gmID = jenv->GetMethodID( 
                           jenv->FindClass("java/lang/Throwable"),
                           "getMessage",
                           "()Ljava/lang/String;");


        jstring jerrStr = (jstring)jenv->CallObjectMethod(ex,gmID);
        // now you can look at the error message string 

        if (jerrStr != NULL){ // make sure getMessage() didn't return null
            const char *errStr = jenv->GetStringUTFChars(jerrStr,0);
            result = errStr;
            jenv->ReleaseStringUTFChars(jerrStr, errStr);
        } else {
            result = "null";
        }

        return TRUE;
    } else {
        return FALSE;
    }
}

BOOL GetJRESystemProperty(JNIEnv *env, string propname, string &propval, string &errmessage)
{
    // now check for minimum JRE version requirement
    jclass cls = env->FindClass("java/lang/System");
    if (cls == NULL){
        errmessage = "Unable to interact with Java Virtual Machine - please visit www.java.com and confirm that your Java installation is valid.";
        return FALSE;
    }

    jmethodID mid = env->GetStaticMethodID(cls, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;");
    if (mid == NULL){
        errmessage = "Unable to obtain Java runtime system properties - please visit www.java.net and confirm that your Java installation is valid.";
        return FALSE;
    }

    jstring propName = env->NewStringUTF( propname.c_str() );
    jstring result = (jstring) env->CallStaticObjectMethod(cls, mid, propName);
    const char* utfResult = env->GetStringUTFChars( result, NULL );

    if (utfResult == NULL){
        errmessage = "Unable to obtain Java runtime system property " + propname + " - please visit www.java.net and confirm that your Java installation is valid.";
        return FALSE;
    }

    propval = utfResult;
    env->ReleaseStringUTFChars( result, utfResult );

    return TRUE;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) {


    JNIEnv *env;
    JavaVM *jvm;
    jint jintVMStartupReturnValue;
    jclass jclassStartup;
    jmethodID midStartup;


    // Path Determination


    // --- application home
    char home[2000];
    if (!GetApplicationHome(home, sizeof(home))) {
        vShowError("Unable to determine application home.");
        return 0;
    }
    string sAppHome(home);
    string sOption_AppHome = "-Dapplication.home=" + sAppHome;


    string sJREPath = GetJREPath();


    // --- VM Path
    string sRuntimePath = sJREPath + "\\bin\\client\\"; // must contain jvm.dll
    string sJVMpath = sRuntimePath + "jvm.dll";

    // --- boot path
    string sBootPath = sJREPath + "\\lib";
    string sOption_BootPath = "-Dsun.boot.class.path=" + sBootPath;


    // --- class path
    //string sClassPath = sAppHome + "\\lib;" + sAppHome + "\\lib\\" + APP_JAR + ";" + sAppHome + "\\lib\\log4j-1.2.7.jar";

    string cpRoot = sAppHome + "\\";
    string sClassPath = GetClassPath(cpRoot);

    string sOption_ClassPath = "-Djava.class.path=" + sClassPath;

    string sOption_JavaLibraryPath = "-Djava.library.path=" + sAppHome + "\\lib";

    int maxHeapBM = 768;

    int argStart = 1; // the first argument passed in that should be passed along to the JVM
    if(__argc > 1){
        string maxheapstr = __argv[1];
        if (maxheapstr.substr(0, 9).compare("/maxheap=") == 0){
            maxheapstr = maxheapstr.substr(9);
            maxHeapBM = atoi(maxheapstr.c_str());
            argStart++;
        }
    }

    // we now use adaptive max heap size determination - we try for 768MB of heap, but if we don't get it, we can back off and use less instead of failing the launch
    // note: we had problems going for 1024 heap at TrueNorth - it would throttle back to 848 and fail with error -4 no matter what I did
    int maxHeapMB = getMaxHeapAvailable(62, maxHeapBM);
    stringstream ss;
    ss << "-Xmx";
    ss << maxHeapMB;
    ss << "m";
    string sOption_HeapSpace = ss.str();

    string sOption_PermSize = "-XX:MaxPermSize=62m";

    string sOption_HeapDump = "-XX:+HeapDumpOnOutOfMemoryError";

    if (strstr(szCmdLine, "/launcher_verbose") != NULL){
        string msg = "App Home = ";
        msg += sAppHome;
        msg += "\nJRE Path = ";
        msg += sJREPath;
        msg += "\nRuntime Path = ";
        msg += sRuntimePath;
        msg += "\nClass Path = ";
        msg += sClassPath;
        msg += "\nHeap argument = ";
        msg += sOption_HeapSpace;
        msg += "\nPermsize argument = ";
        msg += sOption_PermSize;
        msg += "\nHeap dump = ";
        msg += sOption_HeapDump;
        msg += "\njava.library.path = ";
        msg += sOption_JavaLibraryPath;
        msg += "\nCommand line = ";
        msg += szCmdLine;

        FILE *f = fopen("launcher.txt", "w");
        fprintf(f, "%s", msg.c_str());
        fclose(f);

        MessageBox(0, msg.c_str(), "Launcher Verbose Info", MB_OK);

    }

    // setup VM options
    // vAddOption(string("-verbose"));
    vAddOption(sOption_ClassPath);
    vAddOption(sOption_AppHome);

    vAddOption(sOption_HeapSpace);
    vAddOption(sOption_PermSize);
    vAddOption(sOption_HeapDump);
    vAddOption(sOption_JavaLibraryPath);

    // initialize args
    JavaVMInitArgs vm_args;
    vm_args.version = 0x00010002;
    vm_args.options = vm_options;
    vm_args.nOptions = mctOptions;
    vm_args.ignoreUnrecognized = JNI_TRUE;


    // need to diddle with paths to ensure that jvm can find correct libraries - see http://www.duckware.com/tech/java6msvcr71.html
    string sBinPath = sJREPath + "\\bin";
    char originalCurrentDirectory[4096];
    GetCurrentDirectory(4095, originalCurrentDirectory);

    SetCurrentDirectory(sBinPath.c_str());

    // Dynamic binding to SetDllDirectory()
    typedef BOOL (WINAPI *LPFNSDD)(LPCTSTR lpPathname);
    HINSTANCE hKernel32 = GetModuleHandle("kernel32");
    LPFNSDD lpfnSetDllDirectory = (LPFNSDD)GetProcAddress(hKernel32, "SetDllDirectoryA");
    if (lpfnSetDllDirectory){
        lpfnSetDllDirectory(sBinPath.c_str());
    }

    // load jvm library
    HINSTANCE hJVM = LoadLibrary(sJVMpath.c_str());

    SetCurrentDirectory(originalCurrentDirectory);
    if (lpfnSetDllDirectory){
        lpfnSetDllDirectory(NULL);
    }

    if( hJVM == NULL ){
        vShowJREError("Java does not appear to be installed on this machine.  Click OK to go to www.java.com where you can download and install Java");
        return 0;
    }


    // try to start 1.2/3/4 VM
    // uses handle above to locate entry point
    CreateJavaVM lpfnCreateJavaVM = (CreateJavaVM)
    GetProcAddress(hJVM, "JNI_CreateJavaVM");
    jintVMStartupReturnValue = (*lpfnCreateJavaVM)(&jvm, &env, &vm_args);

    // test for success
    if (jintVMStartupReturnValue < 0) {
        stringstream ss;
        ss << "There is a problem with the 32 bit Java installation on this computer (";
        ss << jintVMStartupReturnValue;
        ss << ").  Click OK to go to www.java.com where you can download and re-install 32 bit Java";

        vShowJREError(ss.str());
        // I don't think we should destroy the VM - it never was created...
        //vDestroyVM(env, jvm);
        return 0;
    }


    //now check for minimum jvm version 
    string version = "";
    string errormsg = "";
    if (!GetJRESystemProperty(env, "java.specification.version", version, errormsg)){
        vShowJREError(errormsg);
        vDestroyVM(env, jvm);
        return 0;
    }

    double verf = atof(version.c_str());
    if (verf < 1.599f){
        string sErrorMessage = "This application requires Java Runtime version 1.6 or above, but your runtime is version " + version + "\n\nClick OK to go to www.java.com and update to the latest Java Runtime Environment";
        vShowJREError(sErrorMessage);
        vDestroyVM(env, jvm);
        return 0;
    }


    // find startup class
    string sStartupClass = STARTUP_CLASS;
    // notice dots are translated to slashes
    jclassStartup = env->FindClass(sStartupClass.c_str());
    if (jclassStartup == NULL) {
        string sErrorMessage = "Unable to find startup class [" + sStartupClass + "]";
        vShowError(sErrorMessage);
        vDestroyVM(env, jvm);
        return 0;
    }


    // find startup method
    string sStartupMethod_Identifier = "main";
    string sStartupMethod_TypeDescriptor =
    "([Ljava/lang/String;)V";
    midStartup = 
    env->GetStaticMethodID(jclassStartup,
    sStartupMethod_Identifier.c_str(),
    sStartupMethod_TypeDescriptor.c_str());
    if (midStartup == NULL) {
        string sErrorMessage =
            "Unable to find startup method ["
            + sStartupClass + "."
            + sStartupMethod_Identifier
            + "] with type descriptor [" +
            sStartupMethod_TypeDescriptor + "]";
        vShowError(sErrorMessage);
        vDestroyVM(env, jvm);
        return 0;
    }


    // create array of args to startup method
    jstring jstringExampleArg;
    jclass jclassString;
    jobjectArray jobjectArray_args;


    jstringExampleArg = env->NewStringUTF("example string");
    if (jstringExampleArg == NULL){
        vDestroyVM(env, jvm);
        return 0;
    }
    jclassString = env->FindClass("java/lang/String");
    jobjectArray_args = env->NewObjectArray(__argc-argStart, jclassString, jstringExampleArg);
    if (jobjectArray_args == NULL){
        vDestroyVM(env, jvm);
        return 0;
    }

    int count;
    for (count = argStart; count < __argc; count++){
        env->SetObjectArrayElement(jobjectArray_args, count-1, env->NewStringUTF(__argv[count]));
    }

    // call the startup method -
    // this starts the Java program
    env->CallStaticVoidMethod(jclassStartup, midStartup, jobjectArray_args);

    string errstr;
    if (GetExceptionString(env, errstr)){
        vShowError(errstr);
    }

    // attempt to detach main thread before exiting
    if (jvm->DetachCurrentThread() != 0) {
        vShowError("Could not detach main thread.\n");
    }

    // this call will hang as long as there are
    // non-daemon threads remaining
    jvm->DestroyJavaVM();


    return 0;

}


void vDestroyVM(JNIEnv *env, JavaVM *jvm)
{
    if (env->ExceptionOccurred()) {
        env->ExceptionDescribe();
    }
    jvm->DestroyJavaVM();
}


void vShowError(string sError) {
    MessageBox(NULL, sError.c_str(), "Startup Error", MB_OK);
}

void vShowJREError(string sError) {
    MessageBox(NULL, sError.c_str(), "Startup Error", MB_OK);
    ShellExecute(NULL, "open", "http://www.java.com", NULL, NULL, SW_SHOWNORMAL);
}


/* Shows an error message in an OK box with the
system GetLastError appended in brackets */
void vShowLastError(string sLocalError) {
    LPVOID lpSystemMsgBuf;
    FormatMessage(  FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                    NULL,
                    GetLastError(),
                    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                    (LPTSTR) &lpSystemMsgBuf, 0, NULL );
    string sSystemError = string((LPTSTR)lpSystemMsgBuf);
    vShowError(sLocalError + " [" + sSystemError + "]");
}


void vAddOption(string& sValue) {
    mctOptions++;
    if (mctOptions >= mctOptionCapacity) {
        if (mctOptionCapacity == 0) {
            mctOptionCapacity = 3;
            vm_options = (JavaVMOption*)malloc(mctOptionCapacity * sizeof(JavaVMOption));
        } else {
            JavaVMOption *tmp;
            mctOptionCapacity *= 2;
            tmp = (JavaVMOption*)malloc(mctOptionCapacity * sizeof(JavaVMOption));
            memcpy(tmp, vm_options, (mctOptions-1) * sizeof(JavaVMOption));
            free(vm_options);
            vm_options = tmp;
        }
    }
    vm_options[mctOptions-1].optionString = (char*)sValue.c_str();
}


/* If buffer is "c:\app\bin\java",
* then put "c:\app" into buf. */
jboolean GetApplicationHome(char *buf, jint sz) {
    char *cp;
    GetModuleFileName(0, buf, sz);
    *strrchr(buf, '\\') = '\0';
    if ((cp = strrchr(buf, '\\')) == 0) {
        // This happens if the application is in a
        // drive root, and there is no bin directory.
        buf[0] = '\0';
        return JNI_FALSE;
    }
    return JNI_TRUE;
}

string GetClassPath(string root){
    string rootWithBackslash = root;

    if (rootWithBackslash[rootWithBackslash.length()-1] != '\\')
        rootWithBackslash += "\\";

    string cp = rootWithBackslash + "classes\\"; //first entry in the cp

    string libPathWithBackslash = rootWithBackslash + "lib\\";

    // now find all jar files...
    string searchSpec = libPathWithBackslash;

    searchSpec = libPathWithBackslash + "*.jar";


    WIN32_FIND_DATA fd;
    HANDLE find = FindFirstFile(searchSpec.c_str(), &fd); 
    while (find != NULL){
        cp += ";";
        cp += libPathWithBackslash;
        cp += fd.cFileName;
        if (!FindNextFile(find, &fd)){
            FindClose(find);
            find = NULL;
        }
    }

    return cp;
}

string GetJREPath(){

    // first, check for JRE in application directory
    char home[2000];
    if (!GetApplicationHome(home, sizeof(home))) {
        vShowError("Unable to determine application home.");
        return 0;
    }
    string sJREPath(home);
    sJREPath += "\\jre";

    if (PathExists(sJREPath)){
        return sJREPath;
    }

/* - don't check JAVA_HOME - it may be incorrect...
    // next, check the JAVA_HOME environment variable
    GetEnvironmentVariable("JAVA_HOME", home, sizeof(home));
    sJREPath = home;

    if (PathExists(sJREPath)){
        return sJREPath;
    }

*/

    // next, check registry
    HKEY hKeyJRERoot;
    HKEY hKeyJREInstance;
    DWORD dwType;
    DWORD dwSize;
    BYTE *pData;
    string valueName;
    string value;
    LONG regRslt;

    sJREPath = "";

    regRslt = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\JavaSoft\\Java Runtime Environment", 0, KEY_READ, &hKeyJRERoot);

    if (regRslt == ERROR_SUCCESS){

        valueName = "CurrentVersion";

        regRslt = RegQueryValueEx(hKeyJRERoot, valueName.c_str(), NULL, &dwType, NULL, &dwSize);

        if (regRslt == ERROR_SUCCESS){
            pData = (BYTE *)malloc(dwSize);

            value = "";
            regRslt = RegQueryValueEx(hKeyJRERoot, valueName.c_str(), NULL, &dwType, pData, &dwSize);

            if (regRslt == ERROR_SUCCESS){
                value = (LPCSTR)pData;
            }

            free(pData);

            if (value != ""){

                regRslt = RegOpenKeyEx(hKeyJRERoot, value.c_str(), 0, KEY_READ, &hKeyJREInstance);

                if (regRslt == ERROR_SUCCESS){
                    valueName = "JavaHome";
                    value = "";

                    regRslt = RegQueryValueEx(hKeyJREInstance, valueName.c_str(), NULL, &dwType, NULL, &dwSize);

                    if (regRslt == ERROR_SUCCESS){
                        pData = (BYTE *)malloc(dwSize);

                        regRslt = RegQueryValueEx(hKeyJREInstance, valueName.c_str(), NULL, &dwType, pData, &dwSize);

                        if (regRslt == ERROR_SUCCESS){
                            value = (LPCSTR)pData;
                            sJREPath = value;
                        }

                        free(pData);
                    }
                    RegCloseKey(hKeyJREInstance);
                }
            }
        }
        RegCloseKey(hKeyJRERoot);
    }

    return sJREPath;

}

static const DWORD NUM_BYTES_PER_MB = 1024 * 1024;

bool canAllocate(DWORD bytes)
{
    LPVOID lpvBase;

    lpvBase = VirtualAlloc(NULL, bytes, MEM_RESERVE, PAGE_READWRITE);
    if (lpvBase == NULL) return false;

    VirtualFree(lpvBase, 0, MEM_RELEASE);

    return true;
}

int getMaxHeapAvailable(int permGenMB, int maxHeapMB)
{
    DWORD       originalMaxHeapBytes = 0;
    DWORD       maxHeapBytes = 0;
    int         numMemChunks = 0;
    SYSTEM_INFO     sSysInfo;
    DWORD       maxPermBytes = permGenMB * NUM_BYTES_PER_MB;     // Perm space is in addition to the heap size
    DWORD       numBytesNeeded = 0;

    GetSystemInfo(&sSysInfo);

    // jvm aligns as follows: 
    // quoted from size_t GenCollectorPolicy::compute_max_alignment() of jdk 7 hotspot code:
    //      The card marking array and the offset arrays for old generations are
    //      committed in os pages as well. Make sure they are entirely full (to
    //      avoid partial page problems), e.g. if 512 bytes heap corresponds to 1
    //      byte entry and the os page size is 4096, the maximum heap size should
    //      be 512*4096 = 2MB aligned.

    // card_size computation from CardTableModRefBS::SomePublicConstants of jdk 7 hotspot code
    int card_shift  = 9;
    int card_size   = 1 << card_shift;

    DWORD alignmentBytes = sSysInfo.dwPageSize * card_size;

    maxHeapBytes = maxHeapMB * NUM_BYTES_PER_MB + 50*NUM_BYTES_PER_MB; // 50 is an overhead fudge factory per https://forums.oracle.com/forums/thread.jspa?messageID=6463655 (they had 28, I'm bumping it 'just in case')

    // make it fit in the alignment structure
    maxHeapBytes = maxHeapBytes + (maxHeapBytes % alignmentBytes);
    numMemChunks = maxHeapBytes / alignmentBytes;
    originalMaxHeapBytes = maxHeapBytes;

    // loop and decrement requested amount by one chunk
    // until the available amount is found
    numBytesNeeded = maxHeapBytes + maxPermBytes; 
    while (!canAllocate(numBytesNeeded) && numMemChunks > 0) 
    {
        numMemChunks --;
        maxHeapBytes = numMemChunks * alignmentBytes;
        numBytesNeeded = maxHeapBytes + maxPermBytes;
    }

    if (numMemChunks == 0) return 0;

    // we can allocate the requested size, return it now
    if (maxHeapBytes == originalMaxHeapBytes) return maxHeapMB;

    // calculate the new MaxHeapSize in megabytes
    return maxHeapBytes / NUM_BYTES_PER_MB;
}
Kevin Day
  • 16,067
  • 8
  • 44
  • 68
7

One more option... I work on a launcher called WinRun4J, which allows you to specify a max heap size as a percentage of the available memory on the machine its running on (ie. it does a check for the amount of memory available and sets the -Xmx parameter dynamically on startup).

The INI option is "vm.heapsize.max.percent". There is also another option "vm.heapsize.preferred", which sets the -Xmx parameter as the maximum available memory on the machine up to this amount.

I believe some of the other launchers (eg. Launch4J, Janel) offer the same functionality.

Peter Smith
  • 753
  • 6
  • 7
  • 1
    http://launch4j.sourceforge.net/ indicates it does, I'm surprised this answer doesn't have more votes it sounds like a perfect solution and it's free... – ShuggyCoUk Jul 21 '09 at 12:51
5

I think you're out of luck :-( The -Xms and -Xmx options don't provide that flexibility.

So I think you will need to wrap your JVM invocation with a script that can determine the maximum amount of memory, and then set -Xmx appropriately (probably a .vbs script using WMI on Windows). Or perhaps it asks the users the first time it's run ?

A bit of a pain, I fear.

Brian Agnew
  • 268,207
  • 37
  • 334
  • 440
  • 1
    I can't imagine I'm the only guy on the planet with this problem. Are Sun/IBM truly that witless? – Ira Baxter Jul 20 '09 at 09:19
  • The trouble with this is that if they are running other stuff on the machine, they will end up swapping to disk, slowing down their applications enormously – oxbow_lakes Jul 20 '09 at 09:19
  • 2
    @Ira - why are they *witless*? Why would you expect an application to (paraphrasing) "need 1Gb, but if that's not available, then 500Mb will do"? If you can make do with 500Mb, why ask for a Gig? – oxbow_lakes Jul 20 '09 at 09:21
  • There are *so* many issues like this it's untrue. No - you're not the only one. I tend to work in environments where each machine is the same (so this isn't an issue) or I have Java-aware users who are able to modify this parameter themselves. I think the complexity of your ultimate solution depends on your audience/clients :-) – Brian Agnew Jul 20 '09 at 09:21
  • @oxbow_lakes. But I'd expect a command-line option to say 'give me 2Gb or max'. That would be very useful. Effectively it's saying 'I may need all the memory you have'. I don't know about the implications re. the memory management within the JVM, I confess. – Brian Agnew Jul 20 '09 at 09:22
  • Not if the application requests memory only when the GC exhausts. In this case, either the app has "enough" right now, and its VM footprint stays stable, or if it needs more, it demands more. The alternative is that applications are not slow, but simply don't run. I had that capability before I had VM :-( – Ira Baxter Jul 20 '09 at 09:23
  • 1
    The request I want to make is, "Give a minimum allocation, then take memory as you need it up to the availble VM size", because I don't know what the data size demand is in advance. And no, I don't have the luck to have Java-aware customers, so asking them to choose is really not an option. – Ira Baxter Jul 20 '09 at 09:26
  • @Ira - agreed. To be clear, the VM size you're talking about is the machine image, not the JVM ? – Brian Agnew Jul 20 '09 at 09:29
  • @Brian: The VM size of interest is the OS-defined VM size. – Ira Baxter Jul 20 '09 at 09:30
  • What the hell is an "OS-defined VM size"? – Michael Borgwardt Jul 20 '09 at 09:32
  • @Michael: What you configure the OS to allow for max VM size. I'm suprised that's unclear. – Ira Baxter Jul 20 '09 at 09:34
  • @oxbox: *witless* because they havent figured out after 10 years of shipping Java/JVM that this is a real problem? – Ira Baxter Jul 20 '09 at 09:36
  • @oxbow_lakes: sorry to fumble your handle. Its late :-{ – Ira Baxter Jul 20 '09 at 09:59
4

I think the easiest way to do this would be to launch the JVM via some wrapper application, which would check system resources to determine memory availability, and then launch the JVM with the appropriate -Xmx parameter.

The question then becomes how that wrapper would be written. It may even be possible for the wrapper app to itself be a JVM, although I don't think the API or system properties would expose the necessary information. Maybe a shell script or your choice could get the information.

skaffman
  • 398,947
  • 96
  • 818
  • 769
  • 1
    For me the real question is why isn't this one of the standard JVM -XM* options? The problem was obvious to us about 30 seconds after we ran out of memory on the first application run. – Ira Baxter Jul 20 '09 at 10:01
  • I can't say I've ever come across the need for this, although I do appreciate the usefulness in some situations. – skaffman Jul 20 '09 at 10:05
  • Based on the dicussions, it appears I have to write a wrapper. Probably can't write it in Java. So now I need a non-Java something to run a Java program. That something is probably different under Windows than it is under Linux, etc. So much for "write once, run anywhere", it now "write once, configure differently everywhere". Takes some of the appeal out of using Java. – Ira Baxter Jul 20 '09 at 10:07
  • This is actually a JVM option, -XX:+AggressiveHeap does automatically set -XM and more. See my answer to the question for more info. – Sindri Traustason Jul 20 '09 at 10:30
  • @Ira: There are many platform-specific JVM options. Noone ever claimed that the JVM itself was cross-platform. – skaffman Jul 20 '09 at 10:37
  • @skaffman That's not the point. The mantra for Java is "write once run anywhere". That's what my boss hears. Then he hears we have trouble getting it to run anywhere. [I've been chasing after a VM size cure for several months] What's a reasonable boss to think given that information? – Ira Baxter Jul 20 '09 at 14:22
  • It *is* the point. The "write once run anywhere" mantra applies only to the application code, *not* the VM executable. – skaffman Jul 20 '09 at 14:43
  • @Skaffman. Only if you don't object to the fine-print game in contracts. (Lest this turn into a flame war, I've said enough). – Ira Baxter Jul 20 '09 at 15:19
1

if you have a lot of time on your hand you could try the following :

Try to obtain what is the needed memory vs input dataset. With this you can split processing in a different set of classes and create a new JVM process to actually process the data. Basically a Manager and a Worker. The Manager would do a basic analysis on the demanded dataset and spawn a Worker with the appropriate memory requirements. You could probably also set your Manager to be aware of the environment and warn the user when they are trying to operate on a dataset their machine cannot handle.

This is pretty much an extension on the answer provided by skaffman but will happen all within the same app as far as the user is concerned.

Newtopian
  • 7,543
  • 4
  • 48
  • 71
  • Doesn't work for us. That analysis to decide how much VM we need, pretty much uses the amount of VM needed. This is true for a lot of complicated computations. – Ira Baxter Mar 12 '11 at 23:04
0

There is two options in the virtual machine arguments that can be used : -Xms to set the memory size at startup and -Xmx to set the maximum memory size...

You can set a low startup memory and a big maximum one, so the VM will allocate new memory only if needed.

Vinze
  • 2,549
  • 3
  • 22
  • 23
  • I *think* the problem is that if you set -Xmx large enough, then it's greater than the memory available and the JVM exits – Brian Agnew Jul 20 '09 at 09:16
  • If you set -Xmx larger that the currently available VM, the JVM chokes on startup and refuses to run. The problem is that the system on which the applicaiton is run isn't ours, and we don't know what that target JVM size is. – Ira Baxter Jul 20 '09 at 09:16
  • ok I didn't understood the question... the question is how to create a launch script if you don't know the max available memory. – Vinze Jul 20 '09 at 09:33
  • 1
    -Xmx only virtually allocates and can be well in excess of physical memory. Why was this response voted down? – Jé Queue Apr 28 '10 at 23:06
0

I don't think you can do what you are trying to do; instead you'll have to ship instructions specific to your customers, their systems and their demands of how they can modify your .cmd file to allow for more memory.

Of course, if your product is aimed at very non-technical users, you may wish to hide this behind some more user-friendly config file. E.g.

# HIGH, MEDIUM, LOW - please change as appropriate. The guidelines are:
#                       * HIGH - users who generate 500 items per day
#                       * MEDIUM - 200-500 items etc
memoryUsage=MEDIUM

or possibly deploy different config files depending on which product option a user specifies when they order the product in the first place.

oxbow_lakes
  • 133,303
  • 56
  • 317
  • 449
  • We added a command line option to our tool to basically pass VM size options through. Most of the customers are clueless about this; they expect their application to work without them getting involved, so this simply results in support phone calls. – Ira Baxter Jul 20 '09 at 09:28
  • I don't think it unreasonable for a customer to have to indicate in some way what sort of usage pattern they have to expect: I've modified the answer – oxbow_lakes Jul 20 '09 at 12:27
  • @oxbox: You may not think it unreasonable for customer to do this. But the customers do. And if you force them to guess, and they guess wrong, which they will, they just blame you for having a user-hostile tool. I already have this problem. – Ira Baxter Aug 10 '10 at 14:18
0

I do not think either the Sun or IBM JVM can do this (I know that the AS/400 one can, but that is most likely not relevant to you).

I would suggest using Java WebStart (and before you discard this, then notice that it has been updated with Java 6 u 10 and is much better suited for launching "local" applications and applet) since it allows you to provide a "small instance", "larger instance", "gigantic instance" as links/icons.

You will most likely look into the "inject application in webstart cache" and "offline"options.

Thorbjørn Ravn Andersen
  • 73,784
  • 33
  • 194
  • 347
0

In comments you say that the amount of memory that your application actually depends on the input dataset size provided by the user. This suggests that instead of trying to grab all available virtual memory (which may cause problems for the user's other applications) you should be looking at the input dataset size before you start the JVM and using that to estimate the amount of memory the application will need.

Suppose that the user's machine is configured with modest physical memory and a huge swap space. If you launch the JVM with a huge VM size, it could cause severe "thrashing" as the JVM tries to access data in non-resident pages. By contrast, if you give the JVM something more than the application needs and less than the available physical memory, you should be able to run comfortably without thrashing.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
  • That wasn't what I proposed. What I want is something that allocates a pretty small first amount of VM, and then grew it each time the garbage collector finished with only a small amount of free space (say, 5%). Then VM demand stays small with small data, and grows to handle big data as required. In fact, AFAIK, it already does this. The failure is that I can't specify the upper bound in a target-machine independent way. – Ira Baxter Jul 21 '09 at 08:47
  • I doubt that for any interesting application that it is easy to make an estimate of VM size vs. input size that works reliably. This is the big Oh problem; you might get the O(formula) sort of right, but you need that tuning constant, and if nothing else, different JVMs will have different constants, and different users will have different JVMs. The demand-memory-as-needed scheme is far safer – Ira Baxter Jul 21 '09 at 08:50
0

I read through the threads but didn't see anything which indicated that the application had undergone some sort of profiling. Normally I'd profile the apps under certain conditions to find hot spots in performance or memory usage. There's probably things that could be improved in most cases.

If you could establish the limits and understand the behavior of the application you could be in a position to better tell your customers what they can or cannot do with the application thereby reducing the amount of support calls and giving you a better idea of what minimum or maximum heap size to ship the product with.

Maybe you could start with this: http://www.eclipse.org/mat/

pengtuck
  • 11
  • 1
  • 1
    Thanks for responding; not much traffic on this question recently. ... While in general I'd agree that (memory) profiling might be useful to ensure that memory is not wasted, we feel this application is pretty well engineered. The real problem is that the amount of data it must read and process for a single user can very literally by 2-3 orders of magnitude with a relatively heavy bias towards smaller sizes. This means the app works well with a modest allocation most of the time, and inevitably runs out of memory when that modest allocation meets the large input... – Ira Baxter Jan 08 '10 at 15:14
  • 1
    ... and because it works well with small, when the out-of-memory problem occurs, the users are so used to it running well that they never bother to learn about allocating stuff themselves. And frankly, while we can (and do) ask them to do this reallocation themselves, the entire problem is an artifact of the dumb way that the VM allocator works. If it simply took more memory when needed, there wouldn't be a problem. So, our users are a victim of a dumb design decision on the part of the JVM implementation, and of course we get take support calls as a consequence. – Ira Baxter Jan 08 '10 at 15:16
  • Sounds like you've hit on a hot spot here (the reading part) to me :D. Ask the developers how they are doing the reading and data binding :P. – pengtuck Jan 21 '10 at 04:34
0

Have you looked at running jps to give you the PID for your process and then calling jinfo to change the mx option? Not sure if this will work but it may.

[Edit] This would mean that when you think you have a big dataset, you read the total amount of ram somehow (OS dependent I think. See http://forums.sun.com/thread.jspa?messageID=10306570) or you just increase the size until you don't think it is low anymore (if it blows up first, try to capture and display a helpful message such as "your machine is inadequate, time to make a run to Frys").

David T
  • 31
  • 3
-1

This discussion is moot if you think that your clients can ask for 2-3GB of RAM on their 32-bit machine. The OS and other apps will be taking their pound of flesh to run as well.

Sounds like your app is reaching the point where it needs a 64-bit operating system and lots more RAM.

duffymo
  • 305,152
  • 44
  • 369
  • 561
  • 1
    No. Some users (in fact most) only process small datasets, and are fine with smaller machines. Some have huge datasets. I can't insist they they all have 64 bit systems, and that seems unreasonable if they have only small data. – Ira Baxter Jul 20 '09 at 09:45
  • 2
    PS: Going 64 bits doesn't change the dumb behavior of the -Xmx option. The JVM still chokes at startup if you don't have a number consistent with the specific machine VM configuration. – Ira Baxter Jul 20 '09 at 10:10