Java执行Runtime.exec(shell)报Cannot allocate memory

在Linux下用java的Runtime.getRuntime().exec(cmd)方式,执行shell脚本时,遇到“Cannot allocate memory”的错误。java

网上查询资料整理以下:shell

Cannot allocate memoryless

在Linux上调试一个比较复杂的Java程序,称为JavaA吧,JavaA会频繁的经过Process proc = Runtime.getRuntime().exec(cmd);调用一些外部程序。在系统负载和该程序占用内存都比较大的状况下,会出现调用失败的状况,错误信息是:"Cannot allocate memory"。优化


overcommit_memoryui

经过top发现,Java A大部分时间占用的内存实际并很少,可是占用的虚拟内存很大。立刻修改该程序启动时的JVM参数,将最大内存调的小一些,果真就不出错了。因为JavaA必须在内存中处理大量的数据,内存过小了就可能处理不了,所以这么改是不可行的。spa

上网查的过程当中,发现一些有趣的东西。Linux内核中能够设置内存的overcommit_memory属性(设置方法:echo 1 > /proc/sys/vm/overcommit_memory),意思是Linux内核认为有些程序很保守,老是申请较多的内存,但实际并不使用,所以在设置overcommit后,内核将不检查剩余内存是否够用,直接容许全部的内存分配。可能大部分状况下没问题,可是仔细想一想,仍是有很大的问题,最严重的是改变了malloc的语义,调用者不能经过返回值来判断内存是否分配成功了。另一个问题是,万一内存真的不够了怎么办?Linux中有个特殊的进程,OOM(out-of-memory)进程终止者,其功能就是在内存真的不够时,随机或者根据某些原则杀掉一些进程。选择进程的原则好像不能精确控制,那将是一件很恐怖的事情。。。线程


Runtime.getRuntime().exec(cmd) 的执行流程分析调试

继续上网查,大概意思是Java程序调用外部程序时可能须要分配跟父进程同等大小的内存。这就奇怪了,好比说,我随便调用一下ls命令,也须要不少内存吗?确定是Java调用外部程序的接口里处理比较特殊。嗯,恰好JDK也开源,看看源码去。code

分析SUN JDK 1.5 SRC,找到Runtime.getRuntime().exec(cmd)的执行流程:server

java.lang.Runtime.exec(cmd);

--java.lang.ProcessBuilder.start();

----java.lang.ProcessImpl.start();

------Java_java_lang_UNIXProcess_forkAndExec() in j2se/src/solaris/native/java/lang/UNIXProcess_md.c

--------1). fork(); 2). execvp();

man fork知道,fork产生的子进程须要复制父进程在内存中的全部数据内容(代码段、数据段、堆栈段),因为所有复制开销较大,所以Linux已经采用copy-on-write机制,即只是复制页表,共享内容,在有改变的时候再去申请内存和复制数据。

所以我分析,问题的缘由多是这样的,虽然Linux早已在fork()中采用copy-on-write机制,可是JVM调用fork()后,Java进程里的其它线程每每会被调度回来继续执行,修改了本身的内存,而这个时候execvp()尚未执行,因而悲剧就发生了,内存都要从新复制一遍。

 

解决办法

方法一(不推荐):修改 overcommit_memory值:echo 1 > /proc/sys/vm/overcommit_memory

方法二: 既然问题出在可能会申请分配跟父进程同等大小的内存,那么我限制父进程使用的内存就能够了。前面说了咱们正在开发的JavaA必须使用比较大的内存,但是JavaA不必定是父进程呀,我能够单独运行一个Java程序,称为JavaB吧,由它负责调用外部程序,JavaA调用咱们封装后的接口与之通讯,等待外部程序结束,从而与 Runtime.getRuntime().exec(cmd) 的语义保持一致。这个单独运行的JavaB只须要很小很小的内存,所以不太可能出现没法分配内存,进而没法执行外部程序的问题了。

方法三:针对Tomcat的Web应用,咱们习惯在catalina.sh中进行JVM的内存优化配置:

JAVA_OPTS="$JAVA_OPTS -Djava.awt.headless=true -Dfile.encoding=UTF-8 -server -Xms256m -Xmx1024m -XX:NewSize=64m -XX:MaxNewSize=512m -XX:PermSize=64m -XX:MaxPermSize=512m -XX:+DisableExplicitGC"

注意,咱们要把 -Xms, -XX:NewSize, -XX:PermSize 的内存大小设置一个较小的值,这样才能保证不出现 Cannot allocate memory 异常。

相关文章
相关标签/搜索