让JVM根据需要增长内存需求,直到达到VM限制的大小?

17
我们发布的Java应用程序其内存需求可能会因为处理数据的大小而大幅度变化。如果您不设置最大VM(虚拟内存)大小,很多时候JVM在处理大数据时会因GC失败而退出。
我们希望看到的是,当GC无法提供足够的内存时,JVM请求更多的内存,直到总可用VM用尽。例如,从128Mb开始,并在每次GC失败后按几何(或其他步骤)递增。
JVM(“Java”)命令行允许明确设置最大VM大小(各种-Xm*命令),您可能认为这应该足够了。我们尝试在应用程序中随附的.cmd文件中执行此操作。但是,如果您选择任何特定数字,则会出现以下两种糟糕的情况:1)如果您的数字足够小以适用于大多数目标系统(例如1Gb),则对于大数据而言不够大;2)如果您将其设置得非常大,则JVM会拒绝在实际VM小于指定值的系统上运行。
如何设置Java在需要时使用可用的VM,而又不预先知道那个数字,也不在启动时抓取所有内存?

1
请确保术语使用准确:不存在“可用虚拟机”或“实际虚拟机”这样的概念。您可能是指内存(RAM)。 - Michael Borgwardt
3
也许你对同一句话中的“JVM”和“VM”感到困惑。 JVM指的是Java虚拟机,而“VM”则是操作系统提供的虚拟内存: http://en.wikipedia.org/wiki/Virtual_memory 我感兴趣的是操作系统允许进程使用的最大虚拟内存,通常这个值与操作系统为所有进程配置的总虚拟内存相同。 - Ira Baxter
啊,现在我明白了。把那些相同的缩写放在一起确实很令人困惑。 - Michael Borgwardt
JVM内存选项的限制是Java中一个非常大的缺陷。虽然这里提供的更好的答案是巧妙的技巧,但它们仍然只是技巧。我希望在Java 7中能看到更优雅的内存管理。 - James McMahon
我们这里有一些应用程序也遇到了同样的问题;大多数情况下,它会处理小数据集,并且我们不想给它太多内存,但是偶尔需要处理大数据集时就需要更多内存。目前为止我们还没有解决方案,我们打算创建一个包装器来检查要处理的数据大小,并使用相应的参数启动Java进程...真的只是一个糟糕的hack解决方案,但我们看不到其他办法。 - JohnSmith
@JohnSmith - 我们也看不到更好的解决方案。在我们的情况下,情况更糟:您无法通过检查数据来确定VM需求的大小,而不会实际上在数据本身上执行程序。因此,我们只想让VM需求随着需要而增长。在这里看到的最不可怕的答案之一是包装器,它查看机器的VM大小并设置命令行参数。 - Ira Baxter
14个回答

13
您还可以使用选项:-XX:+AggressiveHeap 根据文档 [1]:
-XX:+AggressiveHeap 选项检查机器资源(内存大小和处理器数量),并尝试设置各种参数以使其最适合长时间运行、内存分配密集型作业。它最初是针对具有大量内存和大量 CPU 的机器而设计的,但在 J2SE 平台,1.4.1 版本及更高版本中,即使在四个处理器的机器上也显示出其有用性。使用此选项时,会使用吞吐量收集器(-XX:+UseParallelGC)以及自适应调整大小(-XX:+UseAdaptiveSizePolicy)。机器上的物理内存必须至少为 256MB 才能使用 AggressiveHeap。初始堆的大小是基于物理内存的大小计算的,并尝试将物理内存最大限度地用于堆(即,算法尝试使用几乎与总物理内存一样大的堆)。
[1]: http://java.sun.com/docs/hotspot/gc1.4.2/#4.2.2。AggressiveHeap|outline

3
采用此选项后,算法会尝试使用几乎等于总物理内存大小的堆。如果用户尝试运行其他应用程序会发生什么?也许该选项对于专用服务器环境有用? - zkarthik
3
任何 -XX 选项都有可能在没有警告的情况下更改(或消失)。它们不是 JVM 的公共 API,如果可能的话,您应该避免将公共产品与它们捆绑在一起。 - oxbow_lakes
3
确实,在Java 1.5及以后的版本中似乎无法使用"-XX:+AggressiveHeap"选项。 - Stephen C
1
@zkarthik 我认为这个选项只对那些几乎什么都不做,只运行一个Java虚拟机的服务器有用。我认为物理内存也不包括交换空间。 - Sindri Traustason
zharthik:回答你的问题,简单来说..我认为我们所提到的选项只会设置最大堆大小,我不认为他会使用相同的值作为堆大小的初始或最小值。在这种情况下,使用虚拟内存的操作系统将不断地将数据从内存交换到硬盘上。最终,如果所有应用程序所需的总内存太多,而操作系统没有足够的时间和资源来进行交换,则操作系统会崩溃。我觉得Java有一个“最大堆大小”的概念并强制执行这个规定是可疑的。 - Martin Andersson
显示剩余4条评论

8
我们有一个小型的C应用程序,我们通过JNI启动所有Java应用程序。这样可以实现以下功能:
  1. 拥有有意义的进程名称(在Windows下尤为重要)
  2. 拥有自己的图标(同样对Windows很重要)
  3. 动态构建类路径(我们解析/lib文件夹中的内容以自动包含所有jar包)
对于我们的应用程序,我们只是硬编码堆限制,但是你可以根据可用内存轻松动态配置最大堆大小。
这种小应用程序实际上非常容易实现(使用JNI是最容易的事情之一)。一个好的起点是JDK的源代码(java.exe本身有一个子文件夹,可以使用它-这就是我们所做的)。大多数人会惊讶地发现,java.exe是一个很小的应用程序(少于200行代码),它只是调用JNI并传递命令行参数(一旦你开始自己启动东西,甚至使用名为main()的方法也是相当可选的)。
这里有一段代码,不仅启动了JVM等等,而且还根据计算机可用RAM确定了最大堆空间。这是一段很长的代码,不算很漂亮,但这是经过实战考验的代码-它已经在几百个安装中使用了将近十年的时间。享受吧:
#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。你现在能发布些代码片段吗?我知道那是很久以前的事情了! - ayush

8
最大虚拟机大小确实能够满足这个需求(它设置了最大值,但虚拟机只会逐步地采取必要的步骤),但如果您需要多个配置,除了提供不同的“cmd”文件之外,我真的看不到其他方式(尽管我会再搜索一下)。
【编辑】 使用第一个程序/脚本(甚至是另一个Java程序),它将检查系统的可用资源,然后根据从系统中检索到的内容仅调用具有适当-Xm的程序。 这样它就可以适应机器,即使您事先不知道它们。可能是一个好主意...
【第二次编辑】 好的,这已经被skaffman提出过了,我的错。

1
不是你的错,如果你看一下时间戳,你的回答比他的更早到达。然而,讨论似乎正在围绕Brian的回复展开,与你的回答并没有什么不同。感谢你的参与。 - Ira Baxter

7

还有一种选择......我在一个名为WinRun4J的启动器上工作,它允许您将最大堆大小指定为运行机器上可用内存的百分比(即它会检查可用内存量并在启动时动态设置-Xmx参数)。

INI选项是“vm.heapsize.max.percent”。还有另一个选项“vm.heapsize.preferred”,它将-Xmx参数设置为机器上可用内存的最大值,最高可达到此金额。

我相信其他一些启动器(例如Launch4J、Janel)也提供了相同的功能。


1
http://launch4j.sourceforge.net/ 表明它可以做到,我很惊讶这个答案没有更多的投票,因为它听起来像一个完美的解决方案,而且是免费的... - ShuggyCoUk

5
我觉得你没那么幸运 :-( -Xms-Xmx 选项并不提供这种灵活性。因此,我认为你需要编写一个脚本来确定最大内存量,然后适当地设置 -Xmx(在 Windows 上可能是一个 .vbs 脚本,使用 WMI)。或者它在第一次运行时询问用户?恐怕有点麻烦。

1
我无法想象我是地球上唯一遇到这个问题的人。难道Sun/IBM真的那么愚蠢吗? - Ira Baxter
2
@Ira - 他们为什么这么“愚蠢”?你为什么期望一个应用程序(换句话说)“需要1Gb,但如果没有那么多可用空间,那么500Mb也可以”?如果你只需要500Mb,为什么要请求1G的空间呢? - oxbow_lakes
1
我想提出的请求是,“给出最小分配,然后根据可用虚拟机空间需要多少就申请多少内存”,因为我不知道数据大小的需求。而且,我没有那么幸运有Java-aware的客户,所以要求他们选择真的不是一个选项。 - Ira Baxter
@Brian:感兴趣的虚拟机大小是操作系统定义的虚拟机大小。 - Ira Baxter
@oxbox:因为他们在十年的Java/JVM发布后仍然没有意识到这是一个真正的问题,所以他们很愚蠢。 - Ira Baxter
显示剩余8条评论

4

我认为最简单的方法是通过某个包装应用程序启动JVM,该应用程序将检查系统资源以确定内存可用性,然后使用适当的 -Xmx 参数启动JVM。

那么问题就变成了如何编写这个包装器。它甚至可以是一个JVM本身,尽管我不认为API或系统属性会公开必要的信息。也许一个shell脚本或者你选择的其他语言可以获取这些信息。


1
对我来说,真正的问题是为什么这不是标准JVM -XM*选项之一?在第一次应用程序运行时,我们大约30秒后就意识到了内存不足的问题。 - Ira Baxter
我不能说我曾经遇到过这种需要,尽管我确实欣赏在某些情况下的有用性。 - skaffman
根据讨论,看起来我需要编写一个包装器。可能不能用Java编写它。所以现在我需要一个非Java的东西来运行Java程序。这个东西在Windows下可能与Linux下不同,等等。所以“一次编写,到处运行”的理念已经不存在了,现在是“一次编写,在每个地方都要进行不同的配置”。这让使用Java的吸引力大打折扣。 - Ira Baxter
这实际上是一个JVM选项,-XX:+ AggressiveHeap会自动设置-XM等更多选项。有关更多信息,请参见我的答案。 - Sindri Traustason
@Skaffman。只有在您不反对合同中的细则游戏时才可以这样做。(为了避免引发争吵,我已经说得足够多了)。 - Ira Baxter
显示剩余3条评论

1

如果你有很多时间,可以尝试以下方法:

尝试获得所需内存与输入数据集的比例。通过这个比例,你可以将处理过程分成不同的类,并创建一个新的JVM进程来处理数据。基本上是一个管理者和一个工作者。管理者会对被请求的数据集进行基本分析,并生成一个具有适当内存要求的工作者。你还可以设置你的管理者,使其了解环境并在用户尝试操作其计算机无法处理的数据集时发出警告。

这基本上是对skaffman提供的答案的扩展,但从用户的角度来看,所有操作都将在同一个应用中完成。


对我们来说不起作用。决定我们需要多少虚拟机的分析基本上使用了所需虚拟机的数量。这在许多复杂计算中都是正确的。 - Ira Baxter

0

我认为你不能做你试图做的事情;相反,你将不得不为你的客户、他们的系统和他们对如何修改你的.cmd文件以允许更多内存的需求提供特定的发货说明

当然,如果你的产品面向非常非技术性的用户,你可能希望在一些更用户友好的配置文件后面隐藏这个。例如:

# 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

或者根据用户在首次订购产品时指定的产品选项,可能部署不同的配置文件。


我们向我们的工具添加了一个命令行选项,以基本上通过虚拟机大小选项。大多数客户对此一无所知;他们期望应用程序在不牵涉到他们的情况下正常工作,因此这只会导致支持电话的增加。 - Ira Baxter
我认为要求客户以某种方式指示他们所期望的使用模式并不是不合理的:我已修改了答案。 - oxbow_lakes
@oxbox:你可能认为让用户这样做并不过分。但用户们并不这么认为。如果你强迫他们去猜测,他们就会猜错,而他们会因为使用不友好的工具而责怪你。我已经遇到了这个问题。 - Ira Baxter

0

你有没有考虑运行 jps 命令来获取进程的 PID,然后调用 jinfo 命令更改 mx 选项?不确定是否有效,但可以尝试。

[编辑] 这意味着当你认为自己有一个大数据集时,你会以某种方式读取总内存量(我认为这取决于操作系统。请参见http://forums.sun.com/thread.jspa?messageID=10306570),或者您只需增加大小,直到您不再认为它很低(如果首次失败,请尝试捕获和显示有用的消息,例如“您的计算机不足,现在是去Frys的时候了”)。


0

我阅读了这些帖子,但没有看到任何表明该应用程序经过某种配置文件的迹象。通常,在特定条件下对应用程序进行分析以查找性能或内存使用热点。在大多数情况下,可能有一些可以改进的地方。

如果您能够确定应用程序的限制并理解其行为,那么您将能够更好地告诉客户他们可以或不可以使用该应用程序,从而减少支持电话的数量,并为您提供有关产品最小或最大堆大小的更好的想法。

也许您可以从这里开始:http://www.eclipse.org/mat/


1
感谢回复;最近这个问题的流量不多。虽然我一般同意(内存)分析可能有用来确保内存不被浪费,但我们觉得这个应用程序设计得非常好。真正的问题是它必须读取和处理单个用户的数据量可以因为大小差异而变化两到三个数量级,而且相对较小的尺寸比重很大。这意味着应用程序在大部分时间使用适度的内存分配运行良好,但当适度的分配遇到大输入时,不可避免地会耗尽内存。 - Ira Baxter
1
由于它能够很好地处理小型问题,当内存不足时,用户已经习惯了它的良好运行状态,从未费心学习如何自己分配资源。坦白说,虽然我们可以(并且确实)要求他们自己重新分配资源,但整个问题是VM分配器工作方式愚蠢的产物。如果需要更多内存,它只需简单地获取即可,这样就不会有问题了。因此,我们的用户成为JVM实现愚蠢设计决策的受害者,当然我们也因此接到了支持电话。 - Ira Baxter
听起来你在这里碰到了一个热点(阅读部分):D。问问开发人员他们如何进行阅读和数据绑定:P。 - pengtuck

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接