java中執(zhí)行aapt命令行錯(cuò)誤
用java的Runtime.getRuntime()。exec(cmd)方式,執(zhí)行aapt命令行解包apk文件時(shí),遇到"Cannot allocate memory"的錯(cuò)誤。
但是直接在linux上使用aapt命令可以正常使用。
網(wǎng)上查詢資料整理如下:
Cannot allocate memory
在Linux上調(diào)試一個(gè)比較復(fù)雜的Java程序,稱為JavaA吧,JavaA會(huì)頻繁的通過(guò)Process proc = Runtime.getRuntime()。exec(cmd);調(diào)用一些外部程序。在系統(tǒng)負(fù)載和該程序占用內(nèi)存都比較大的情況下,會(huì)出現(xiàn)調(diào)用失敗的情況,錯(cuò)誤信息是:"Cannot allocate memory".
overcommit_memory
通過(guò)top發(fā)現(xiàn),JavaA大部分時(shí)間占用的內(nèi)存實(shí)際并不多,但是占用的虛擬內(nèi)存很大。馬上修改該程序啟動(dòng)時(shí)的JVM參數(shù),將最大內(nèi)存調(diào)的小一些,果然就不出錯(cuò)了。由于JavaA必須在內(nèi)存中處理大量的數(shù)據(jù),內(nèi)存太小了就可能處理不了,因此這么改是不可行的。
上網(wǎng)查的過(guò)程中,發(fā)現(xiàn)一些有趣的東西。Linux內(nèi)核中可以設(shè)置內(nèi)存的overcommit_memory屬性,意思是Linux內(nèi)核認(rèn)為有些程序很保守,總是申請(qǐng)較多的內(nèi)存,但實(shí)際并不使用,因此在設(shè)置overcommit后,內(nèi)核將不檢查剩余內(nèi)存是否夠用,直接允許所有的內(nèi)存分配。可能大部分情況下沒(méi)問(wèn)題,但是仔細(xì)想想,還是有很大的問(wèn)題,最嚴(yán)重的是改變了malloc的語(yǔ)義,調(diào)用者不能通過(guò)返回值來(lái)判斷內(nèi)存是否分配成功了。另外一個(gè)問(wèn)題是,萬(wàn)一內(nèi)存真的不夠了怎么辦?Linux中有個(gè)特殊的進(jìn)程,OOM(out-of-memory)進(jìn)程終止者,其功能就是在內(nèi)存真的不夠時(shí),隨機(jī)或者根據(jù)某些原則殺掉一些進(jìn)程。選擇進(jìn)程的原則好像不能精確控制,那將是一件很恐怖的事情…
順道小小八卦一下,當(dāng)時(shí)還有不少人研究如何選擇要?dú)⒌舻倪M(jìn)程,提出了不少的改進(jìn)方法,于是有人看不下去了,說(shuō)了一個(gè)很有意思的故事,某航空公司為了節(jié)省油錢(qián),每次飛行并不加滿油,飛行途中要是超員了就挑一些乘客扔下去,于是大家興高采烈的討論應(yīng)該扔誰(shuí)下去…
Runtime.getRuntime()。exec(cmd)的執(zhí)行流程分析
繼續(xù)上網(wǎng)查,大概意思是Java程序調(diào)用外部程序時(shí)可能需要分配跟父進(jìn)程同等大小的內(nèi)存。這就奇怪了,比如說(shuō),我隨便調(diào)用一下ls命令,也需要很多內(nèi)存嗎?肯定是Java調(diào)用外部程序的接口里處理比較特殊。嗯,
剛好JDK也開(kāi)源,看看源碼去。
分析SUN JDK 1.5 SRC,找到Runtime.getRuntime()。exec(cmd)的執(zhí)行流程:
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產(chǎn)生的子進(jìn)程需要復(fù)制父進(jìn)程在內(nèi)存中的所有數(shù)據(jù)內(nèi)容(代碼段、數(shù)據(jù)段、堆棧段),
由于全部復(fù)制開(kāi)銷較大,因此Linux已經(jīng)采用copy-on-write機(jī)制,即只是復(fù)制頁(yè)表,共享內(nèi)容,在有改變的時(shí)候再去申請(qǐng)內(nèi)存和復(fù)制數(shù)據(jù)雅思答案
因此我分析,問(wèn)題的原因可能是這樣的,雖然Linux早已在fork()中采用copy-on-write機(jī)制,但是JVM調(diào)用fork()后,Java進(jìn)程里的其它線程往往會(huì)被調(diào)度回來(lái)繼續(xù)執(zhí)行,修改了自己的內(nèi)存,而這個(gè)時(shí)候
execvp()還沒(méi)有執(zhí)行,于是悲劇就發(fā)生了,內(nèi)存都要重新復(fù)制一遍。
解決辦法
最后說(shuō)說(shuō)解決辦法,既然問(wèn)題出在可能會(huì)申請(qǐng)分配跟父進(jìn)程同等大小的內(nèi)存,那么我限制父進(jìn)程使用的內(nèi)存就可以了。前面說(shuō)了我們正在開(kāi)發(fā)的JavaA必須使用比較大的內(nèi)存,可是JavaA不一定是父進(jìn)程呀,我可以單獨(dú)運(yùn)行一個(gè)Java程序,稱為JavaB吧,由它負(fù)責(zé)調(diào)用外部程序,JavaA調(diào)用我們封裝后的接口與之通信,等待外部程序結(jié)束,從而與Runtime.getRuntime()。exec(cmd)的語(yǔ)義保持一致。這個(gè)單獨(dú)運(yùn)行的JavaB只需要很小很小的內(nèi)存,因此不太可能出現(xiàn)無(wú)法分配內(nèi)存,進(jìn)而無(wú)法執(zhí)行外部程序的問(wèn)題了雅思改分
posted on 2013-08-17 09:41 好不容易 閱讀(580) 評(píng)論(0) 編輯 收藏