本站首页    管理页面    写新日志    退出


«August 2025»
12
3456789
10111213141516
17181920212223
24252627282930
31


公告

戒除浮躁,读好书,交益友


我的分类(专题)

日志更新

最新评论

留言板

链接

Blog信息
blog名称:邢红瑞的blog
日志总数:523
评论数量:1142
留言数量:0
访问次数:9687361
建立时间:2004年12月20日




[jvm]未公开的mustang核心秘密(六):java中Runtime exec的实现
原创空间,  软件技术,  电脑与网络

邢红瑞 发表于 2009/3/25 10:11:22

 偶尔在linux使用Runtime.exec(),偶尔出这个问题java.io.IOException: java.io.IOException: Cannot allocate memoryjava.io.IOException: java.io.IOException: Cannot allocate memoryat java.lang.UNIXProcess.<init>(UNIXProcess.java:148)at java.lang.ProcessImpl.start(ProcessImpl.java:65)at java.lang.ProcessBuilder.start(ProcessBuilder.java:451)at java.lang.Runtime.exec(Runtime.java:591)at java.lang.Runtime.exec(Runtime.java:429)at java.lang.Runtime.exec(Runtime.java:326)64位的计算机,一共16G内存,4Gswap,java使用java -Xmx12G -Xms12G。发现Runtime.exec()运行是,使用的内存翻倍,如果使用了12G内存,java 运行Runtime.exec()就需要24G内存,linux 就无法分配了。看了一下源码Runtime调用public Process exec(String[] cmdarray, String[] envp, File dir) throws IOException { return new ProcessBuilder(cmdarray)     .environment(envp)     .directory(dir)     .start();    }ProcessBuilder中public Process start() throws IOException { // Must convert to array first -- a malicious user-supplied // list might try to circumvent the security check. String[] cmdarray = command.toArray(new String[command.size()]); for (String arg : cmdarray)     if (arg == null)  throw new NullPointerException(); // Throws IndexOutOfBoundsException if command is empty String prog = cmdarray[0];  SecurityManager security = System.getSecurityManager(); if (security != null)     security.checkExec(prog);  String dir = directory == null ? null : directory.toString();  return ProcessImpl.start(cmdarray,     environment,     dir,     redirectErrorStream);    }}调用ProcessImpl类 调用了create函数 private native long create(String cmdstr,          String envblock,          String dir,          boolean redirectErrorStream,          FileDescriptor in_fd,          FileDescriptor out_fd,          FileDescriptor err_fd);jni函数 windowsJNIEXPORT jlong JNICALLJava_java_lang_ProcessImpl_create(JNIEnv *env, jclass ignored,                                  jstring cmd,                                  jstring envBlock,                                  jstring dir,                                  jboolean redirectErrorStream,                                  jobject in_fd,                                  jobject out_fd,                                  jobject err_fd){    HANDLE inRead   = 0;    HANDLE inWrite  = 0;    HANDLE outRead  = 0;    HANDLE outWrite = 0;    HANDLE errRead  = 0;    HANDLE errWrite = 0;    SECURITY_ATTRIBUTES sa;    PROCESS_INFORMATION pi;    STARTUPINFO si;    LPTSTR  pcmd      = NULL;    LPCTSTR pdir      = NULL;    LPVOID  penvBlock = NULL;    jlong ret = 0;    OSVERSIONINFO ver;    jboolean onNT = JNI_FALSE;    DWORD processFlag;     ver.dwOSVersionInfoSize = sizeof(ver);    GetVersionEx(&ver);    if (ver.dwPlatformId == VER_PLATFORM_WIN32_NT)        onNT = JNI_TRUE;     sa.nLength = sizeof(sa);    sa.lpSecurityDescriptor = 0;    sa.bInheritHandle = TRUE;     if (!(CreatePipe(&inRead,  &inWrite,  &sa, PIPE_SIZE) &&          CreatePipe(&outRead, &outWrite, &sa, PIPE_SIZE) &&          CreatePipe(&errRead, &errWrite, &sa, PIPE_SIZE))) {        win32Error(env, "CreatePipe");        goto Catch;    }     assert(cmd != NULL);    pcmd = (LPTSTR) JNU_GetStringPlatformChars(env, cmd, NULL);    if (pcmd == NULL) goto Catch;     if (dir != 0) {        pdir = (LPCTSTR) JNU_GetStringPlatformChars(env, dir, NULL);        if (pdir == NULL) goto Catch;        pdir = (LPCTSTR) JVM_NativePath((char *)pdir);    }     if (envBlock != NULL) {        penvBlock = onNT            ? (LPVOID) ((*env)->GetStringChars(env, envBlock, NULL))            : (LPVOID) JNU_GetStringPlatformChars(env, envBlock, NULL);        if (penvBlock == NULL) goto Catch;    }     memset(&si, 0, sizeof(si));    si.cb = sizeof(si);    si.dwFlags = STARTF_USESTDHANDLES;    si.hStdInput  = inRead;    si.hStdOutput = outWrite;    si.hStdError  = redirectErrorStream ? outWrite : errWrite;     SetHandleInformation(inWrite, HANDLE_FLAG_INHERIT, FALSE);    SetHandleInformation(outRead, HANDLE_FLAG_INHERIT, FALSE);    SetHandleInformation(errRead, HANDLE_FLAG_INHERIT, FALSE);     if (redirectErrorStream)        SetHandleInformation(errWrite, HANDLE_FLAG_INHERIT, FALSE);     if (onNT)        processFlag = CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT;    else        processFlag = selectProcessFlag(env, cmd);     /* Java and Windows are both pure Unicode systems at heart.     * Windows has both a legacy byte-based API and a 16-bit Unicode     * "W" API.  The Right Thing here is to call CreateProcessW, since     * that will allow all process-related information like command     * line arguments to be passed properly to the child.  We don't do     * that currently, since we would first have to have "W" versions     * of JVM_NativePath and perhaps other functions.  In the     * meantime, we can call CreateProcess with the magic flag     * CREATE_UNICODE_ENVIRONMENT, which passes only the environment     * in "W" mode.  We will fix this later. */     ret = CreateProcess(0,           /* executable name */                        pcmd,        /* command line */                        0,           /* process security attribute */                        0,           /* thread security attribute */                        TRUE,        /* inherits system handles */                        processFlag, /* selected based on exe type */                        penvBlock,   /* environment block */                        pdir,        /* change to the new current directory */                        &si,         /* (in)  startup information */                        &pi);        /* (out) process information */     if (!ret) {        win32Error(env, "CreateProcess");        goto Catch;    }     CloseHandle(pi.hThread);    ret = (jlong)pi.hProcess;    (*env)->SetLongField(env, in_fd,  IO_handle_fdID, (jlong)inWrite);    (*env)->SetLongField(env, out_fd, IO_handle_fdID, (jlong)outRead);    (*env)->SetLongField(env, err_fd, IO_handle_fdID, (jlong)errRead);  Finally:    /* Always clean up the child's side of the pipes */    closeSafely(inRead);    closeSafely(outWrite);    closeSafely(errWrite);     if (pcmd != NULL)        JNU_ReleaseStringPlatformChars(env, cmd, (char *) pcmd);    if (pdir != NULL)        JNU_ReleaseStringPlatformChars(env, dir, (char *) pdir);    if (penvBlock != NULL) {        if (onNT)            (*env)->ReleaseStringChars(env, envBlock, (jchar *) penvBlock);        else            JNU_ReleaseStringPlatformChars(env, dir, (char *) penvBlock);    }    return ret;  Catch:    /* Clean up the parent's side of the pipes in case of failure only */    closeSafely(inWrite);    closeSafely(outRead);    closeSafely(errRead);    goto Finally;}linux fork子进程要传递父进程的所有句柄资源,fork后面紧跟exec,一般copy整个父进程空间的数据到子进程中。fork()需要需要父进程两倍的内存,#ifndef __solaris__#undef fork1#define fork1() fork()#endif JNIEXPORT jint JNICALLJava_java_lang_UNIXProcess_forkAndExec(JNIEnv *env,                                       jobject process,                                       jbyteArray prog,                                       jbyteArray argBlock, jint argc,                                       jbyteArray envBlock, jint envc,                                       jbyteArray dir,                                       jboolean redirectErrorStream,                                       jobject stdin_fd,                                       jobject stdout_fd,                                       jobject stderr_fd){    int errnum;    int resultPid = -1;    int in[2], out[2], err[2], fail[2];    const char **argv = NULL;    const char **envv = NULL;    const char *pprog     = getBytes(env, prog);    const char *pargBlock = getBytes(env, argBlock);    const char *penvBlock = getBytes(env, envBlock);    const char *pdir      = getBytes(env, dir);     in[0] = in[1] = out[0] = out[1] = err[0] = err[1] = fail[0] = fail[1] = -1;     assert(prog != NULL && argBlock != NULL);    if (pprog     == NULL) goto Catch;    if (pargBlock == NULL) goto Catch;    if (envBlock  != NULL && penvBlock == NULL) goto Catch;    if (dir       != NULL && pdir      == NULL) goto Catch;     /* Convert pprog + pargBlock into a char ** argv */    if ((argv = NEW(const char *, argc + 2)) == NULL)        goto Catch;    argv[0] = pprog;    initVectorFromBlock(argv+1, pargBlock, argc);     if (envBlock != NULL) {        /* Convert penvBlock into a char ** envv */        if ((envv = NEW(const char *, envc + 1)) == NULL)            goto Catch;        initVectorFromBlock(envv, penvBlock, envc);    }     if ((pipe(in)   < 0) ||        (pipe(out)  < 0) ||        (pipe(err)  < 0) ||        (pipe(fail) < 0)) {        throwIOException(env, errno, "Bad file descriptor");        goto Catch;    }     resultPid = fork1();    if (resultPid < 0) {        throwIOException(env, errno, "Fork failed");        goto Catch;    }     if (resultPid == 0) {        /* Child process */         /* Close the parent sides of the pipe.           Give the child sides of the pipes the right fileno's.           Closing pipe fds here is redundant, since closeDescriptors()           would do it anyways, but a little paranoia is a good thing. */        /* Note: it is possible for in[0] == 0 */        close(in[1]);        moveDescriptor(in[0], STDIN_FILENO);        close(out[0]);        moveDescriptor(out[1], STDOUT_FILENO);        close(err[0]);        if (redirectErrorStream) {            close(err[1]);            dup2(STDOUT_FILENO, STDERR_FILENO);        } else {            moveDescriptor(err[1], STDERR_FILENO);        }        close(fail[0]);        moveDescriptor(fail[1], FAIL_FILENO);         /* close everything */        if (closeDescriptors() == 0) { /* failed,  close the old way */            int max_fd = (int)sysconf(_SC_OPEN_MAX);            int i;            for (i = FAIL_FILENO + 1; i < max_fd; i++)                close(i);        }         /* change to the new working directory */        if (pdir != NULL && chdir(pdir) < 0)            goto WhyCantJohnnyExec;         if (fcntl(FAIL_FILENO, F_SETFD, FD_CLOEXEC) == -1)            goto WhyCantJohnnyExec;         execvpe(argv[0], argv, envv);     WhyCantJohnnyExec:        /* We used to go to an awful lot of trouble to predict whether the         * child would fail, but there is no reliable way to predict the         * success of an operation without *trying* it, and there's no way         * to try a chdir or exec in the parent.  Instead, all we need is a         * way to communicate any failure back to the parent.  Easy; we just         * send the errno back to the parent over a pipe in case of failure.         * The tricky thing is, how do we communicate the *success* of exec?         * We use FD_CLOEXEC together with the fact that a read() on a pipe         * yields EOF when the write ends (we have two of them!) are closed.         */        errnum = errno;        write(FAIL_FILENO, &errnum, sizeof(errnum));        close(FAIL_FILENO);        _exit(-1);    }     /* parent process */     close(fail[1]); fail[1] = -1; /* See: WhyCantJohnnyExec */     switch (readFully(fail[0], &errnum, sizeof(errnum))) {    case 0: break; /* Exec succeeded */    case sizeof(errnum):        waitpid(resultPid, NULL, 0);        throwIOException(env, errnum, "Exec failed");        goto Catch;    default:        throwIOException(env, errno, "Read failed");        goto Catch;    }     (*env)->SetIntField(env, stdin_fd,  IO_fd_fdID, in [1]);    (*env)->SetIntField(env, stdout_fd, IO_fd_fdID, out[0]);    (*env)->SetIntField(env, stderr_fd, IO_fd_fdID, err[0]);  Finally:    /* Always clean up the child's side of the pipes */    closeSafely(in [0]);    closeSafely(out[1]);    closeSafely(err[1]);     /* Always clean up fail descriptors */    closeSafely(fail[0]);    closeSafely(fail[1]);     free(argv);    free(envv);     releaseBytes(env, prog,     pprog);    releaseBytes(env, argBlock, pargBlock);    releaseBytes(env, envBlock, penvBlock);    releaseBytes(env, dir,      pdir);     return resultPid;  Catch:    /* Clean up the parent's side of the pipes in case of failure only */    closeSafely(in [1]);    closeSafely(out[0]);    closeSafely(err[0]);    goto Finally;}linux下的java实现,是非常的不幸。


阅读全文(7988) | 回复(1) | 编辑 | 精华
 


回复:未公开的mustang核心秘密(六):java中Runtime exec的实现
原创空间,  软件技术,  电脑与网络

CC(游客)发表评论于2010/2/5 18:37:23

你好,我有个问题不太明白,fork之后的内存是“写时复制”的吧? 为什么会报内存不足呢?


个人主页 | 引用回复 | 主人回复 | 返回 | 编辑 | 删除
 


» 1 »

发表评论:
昵称:
密码:
主页:
标题:
验证码:  (不区分大小写,请仔细填写,输错需重写评论内容!)



站点首页 | 联系我们 | 博客注册 | 博客登陆

Sponsored By W3CHINA
W3CHINA Blog 0.8 Processed in 0.391 second(s), page refreshed 144781769 times.
《全国人大常委会关于维护互联网安全的决定》  《计算机信息网络国际联网安全保护管理办法》
苏ICP备05006046号