在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 異常。