« | August 2025 | » | 日 | 一 | 二 | 三 | 四 | 五 | 六 | | | | | | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 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实现,是非常的不幸。 |
|
回复:未公开的mustang核心秘密(六):java中Runtime exec的实现 原创空间, 软件技术, 电脑与网络
CC(游客)发表评论于2010/2/5 18:37:23 |
你好,我有个问题不太明白,fork之后的内存是“写时复制”的吧? 为什么会报内存不足呢? |
|
» 1 »
|