Java的自動內(nèi)存管理機(jī)制給開發(fā)人員帶來了很多的便利,在設(shè)計(jì)、開發(fā)時(shí)可以完全不用考慮要 分配多少內(nèi)存,要記得回收內(nèi)存等,但同時(shí)也帶來了各種各樣的問題,其中最典型的問題就是OOM,大部分Java開發(fā)人員估計(jì)都看到過 java.lang.OutOfMemoryError這樣的錯(cuò)誤信息,在這篇文章中,就來介紹下Sun JDK中有哪幾種OOM、OOM示例、造成OOM的原因的查找、解決以及Sun JDK代碼中處理OOM的方式。
PDF版本請從此下載:http://blog.bluedavy.com/open/Sun-JDK-OOM.pdf
1 OOM的種類
在Sun JDK中運(yùn)行時(shí),Java程序有可能出現(xiàn)如下幾種OOM錯(cuò)誤:
? java.lang.OutOfMemoryError: unable to create new native thread
當(dāng)調(diào)用new
Thread時(shí),如已創(chuàng)建不了線程了,則會拋出此錯(cuò)誤,如果是JDK內(nèi)部必須創(chuàng)建成功的線程,那么會造成Java進(jìn)程退出,如果是用戶線程,則僅拋出
OOM,創(chuàng)建不了的原因通常是創(chuàng)建了太多線程,耗盡了內(nèi)存,通常可通過減少創(chuàng)建的線程數(shù),或通過-Xss調(diào)小線程所占用的棧大小來減少對Java
對外內(nèi)存的消耗。
? java.lang.OutOfMemoryError: request
當(dāng)JNI模塊或JVM內(nèi)部進(jìn)行malloc操作(例如GC時(shí)做mark)時(shí),需要消耗堆外的內(nèi)存,如此時(shí)Java進(jìn)程所占用的地址空間超過限制(例如
windows: 2G,linux: 3G),或物理內(nèi)存、swap區(qū)均使用完畢,那么則會出現(xiàn)此錯(cuò)誤,當(dāng)出現(xiàn)此錯(cuò)誤時(shí),Java進(jìn)程將會退出。
? java.lang.OutOfMemoryError: Java heap space
這是最常見的OOM錯(cuò)誤,當(dāng)通過new創(chuàng)建對象或數(shù)組時(shí),如Java Heap空間不足(新生代不足,觸發(fā)minor GC,還是不夠,觸發(fā)Full GC,還是不夠),則拋出此錯(cuò)誤。
? java.lang.OutOfMemoryError: GC overhead limit execeeded
當(dāng)通過new創(chuàng)建對象或數(shù)組時(shí),如Java
Heap空間不足,且GC所使用的時(shí)間占了程序總時(shí)間的98%,且Heap剩余空間小于2%,則拋出此錯(cuò)誤,以避免Full
GC一直執(zhí)行,可通過UseGCOverheadLimit來決定是否開啟這種策略,可通過GCTimeLimit和GCHeapFreeLimit來控
制百分比。
? java.lang.OutOfMemoryError: PermGen space
當(dāng)加載class時(shí),在進(jìn)行了Full GC后如PermGen空間仍然不足,則拋出此錯(cuò)誤。
對于以上幾種OOM錯(cuò)誤,其中容易造成嚴(yán)重后果的是Out of swap space這種,因?yàn)檫@種會造成Java進(jìn)程退出,而其他幾種只要不是在main線程拋出的,就不會造成Java進(jìn)程退出。
2 OOM示例、原因查找和解決
這些示例的class以及源碼請從http://blog.bluedavy.com/jvm/cases/oom/OOMCases.zip下載,建議在運(yùn)行前不要看源碼,畢竟源碼是簡單的例子,如果直接看源碼的話,可能會少了查找原因的樂趣。
當(dāng)Java程序運(yùn)行時(shí),會有很多種造成OOM的現(xiàn)象,這里面有些會比較容易查找出原因,而有些會非常困難,下面是來看一些OOM的Example。
? Example 1
以-Xms20m -Xmx20m -Xmn10m -XX:+UseParallelGC參數(shù)運(yùn)行com.bluedavy.oom.JavaHeapSpaceCase1
運(yùn)行后在輸出的日志中可看到大量的Full GC信息,以及:
java.lang.OutOfMemoryError: GC overhead limit exceeded
和java.lang.OutOfMemoryError: Java heap space
根據(jù)上面所說的OOM種類,可以知道這是在new對象或數(shù)組時(shí)Java Heap Space不足造成的,對于這種OOM,需要知道的是程序中哪些部分占用了Java Heap。
要知道程序中哪些部分占用了Java Heap,首先必須拿到Java Heap中的信息,尤其是OOM時(shí)的內(nèi)存信息,在Sun
JDK中可通過在啟動參數(shù)上加上-XX:+ HeapDumpOnOutOfMemoryError來獲得OOM時(shí)的Java
Heap中的信息,當(dāng)出現(xiàn)OOM時(shí),會自動生成一個(gè)java_pid[pid].hprof的文件。
于是在啟動參數(shù)上加上上面的參數(shù),再次運(yùn)行JavaHeapSpaceCase1,可看到在當(dāng)前運(yùn)行的路徑下生成了一個(gè)
java_pid10852.hprof(由于pid不同,你看到的可能是不一樣的文件名)的文件,在拿到這個(gè)文件后,就可通過
mat(http://www.eclipse.org/mat/)來進(jìn)行分析了。
用mat打開上面的文件(默認(rèn)情況下mat認(rèn)為heap dump文件應(yīng)以.bin結(jié)尾,因此請改名或以open
file方式打開),打開后點(diǎn)擊dominator_tree,可看到sun.misc.Launcher$AppClassLoader占據(jù)了大部分的
內(nèi)存,繼續(xù)點(diǎn)開看,則可看到是由于com.bluedavy.oom.Caches中有一個(gè)ArrayList,其中存放的對象占據(jù)了大部分的內(nèi)存,因此
解決這個(gè)OOM的辦法是,讓Caches類中放的對象總大小是有限制的,或者限制放入Caches的ArrayList中的對象個(gè)數(shù)。
這種情況造成的OOM,在實(shí)際的場景中當(dāng)使用緩存時(shí)很容易產(chǎn)生,對于所有的緩存、集合大小都應(yīng)給定限制的最大大小,以避免出現(xiàn)緩存或集合太大,導(dǎo)致消耗了過多的內(nèi)存,從而導(dǎo)致OOM。
? Example 2
以-Xms20m -Xmx20m -Xmn10m -XX:+HeapDumpOnOutOfMemoryError執(zhí)行com.bluedavy.oom.JavaHeapSpaceCase2
運(yùn)行后在輸出的日志中可看到大量的Full GC和java.lang.OutOfMemoryError: Java heap space。
同樣,首先用mat打開需要分析的hprof文件,很驚訝的發(fā)現(xiàn)什么都看不出來,Java Heap
Space還很充足,這就奇怪了,還好除了能在OOM時(shí)自動dump出Heap的信息外,還可通過jmap命令手工dump,于是,在運(yùn)行期出現(xiàn)頻繁
Full GC、OOM的時(shí)候,手工通過jmap –dump:file=heap.bin,format=b
[pid]生成一個(gè)heap.bin文件,把這個(gè)heap.bin文件也拿到mat中分析,杯具,還是什么都看不出來,還好,還有一招,就是直接用
jmap
–histo看看到底什么對象占用了大多數(shù)的內(nèi)存,執(zhí)行一次,看到[I占用了最多的內(nèi)存,沒用,因?yàn)闆]法知道這個(gè)[I到底是代碼中哪個(gè)部分創(chuàng)建的,不甘
心,多執(zhí)行幾次,很幸運(yùn),突然看到com.bluedavy.oom.Case2Object占據(jù)了最大的內(nèi)存,于是查找代碼中哪些地方創(chuàng)建了這個(gè)對象,
發(fā)現(xiàn)了代碼中有一個(gè)線程創(chuàng)建了大量的Case2Object,修改即可。
從這個(gè)例子中,可以看到,在分析OOM問題時(shí),一方面是依賴OOM時(shí)dump出來的文件,但這個(gè)文件其實(shí)只會在Java進(jìn)程中第一次出現(xiàn)OOM時(shí)生成,之
后再OOM就不會生成了,這有可能出現(xiàn)真實(shí)的OOM的原因被假的OOM原因給掩蓋掉;另一方面是依賴在出現(xiàn)OOM時(shí)的人工操作,這種人肉方式其實(shí)比較杯
具,因?yàn)橹荒芟鹊鹊筋l繁Full GC、OOM,首先通過jmap
–histo來看看到底什么對象占用了大部分的內(nèi)存(需要多執(zhí)行幾次,以確保正確性),上面的例子比較幸運(yùn),因?yàn)閯偤檬亲远x的對象,如果是原生的類型,
那就只能借助dump文件來分析了,通過jmap
–dump手工dump出Heap文件,然后用MAT分析,但有可能會出現(xiàn)上面例子中的狀況,就是mat分析上也看不出什么,頂多只能看到
unreachable objects里某些對象占用了大部分的內(nèi)存,而通常情況看到的可能都是原生類型,一旦真的碰到j(luò)map
–histo看到的是原生類型占用較多,jmap dump看到的是Java Heap
Space也不滿的話,那只能說杯具了,在這種情況下,唯一能做的是捕捉所有的異常,然后打印,從而判斷OOM是在哪行代碼拋出的。
? Example 3
以-Xms20m -Xmx20m -Xmn10m -XX:+HeapDumpOnOutOfMemoryError執(zhí)行com.bluedavy.oom.JavaHeapSpaceCase3
在控制臺中可看到大量的java.lang.OutOfMemoryError: Java heap
space,把生成的hprof文件放入MAT中進(jìn)行分析,還好看到確實(shí)是Java Heap Space滿了,這就好辦了,點(diǎn)開Dominator
Tree視圖,可看到有一堆的線程,每個(gè)都占用了一定的內(nèi)存,從而導(dǎo)致了OOM,要解決這個(gè)例子中的OOM,有四種辦法:一是減少處理的線程數(shù);二是處理
線程需要消耗的內(nèi)存;三是提升線程的處理速度;四是增加機(jī)器,減少單臺機(jī)器所需承擔(dān)的請求量。
上面這種狀況在系統(tǒng)處理慢的時(shí)候比較容易出現(xiàn)。
? Example 4
以-Xms20m -Xmx20m -Xmn10m -XX:+HeapDumpOnOutOfMemoryError執(zhí)行com.bluedavy.oom.JavaHeapSpaceCase4
在控制臺可看到大量的java.lang.OutOfMemoryError: Java heap
space,把生成的hprof文件放入MAT中進(jìn)行分析,可看到TaskExecutor中的handlers占據(jù)了大量的內(nèi)存,分析代碼,發(fā)現(xiàn)是由于
在task處理完后沒有清除掉對應(yīng)的handler造成的,修改即可解決此OOM問題。
這是個(gè)典型的內(nèi)存泄露的例子,如果不小心持有了本應(yīng)釋放引用的對象,那么就會造成內(nèi)存泄露,這是在編寫Java程序時(shí)需要特別注意的地方。
? Example 5
以-Xms1536m -Xmx1536m -Xss100m執(zhí)行com.bluedavy.oom.CannotCreateThreadCase
在控制臺可看到j(luò)ava.lang.OutOfMemoryError: unable to create new native thread。
對于這種情況,一需要查看目前-Xss的大小,例如在這個(gè)例子中-Xss太大,導(dǎo)致連20個(gè)線程都無法創(chuàng)建,因此可解決的方法是降低-Xss的大??;如果
Xss使用的是默認(rèn)值,那么可通過jstack來查看下目前Java進(jìn)程中是不是創(chuàng)建了過多的線程,或者是java
heap太大,導(dǎo)致os沒剩多少內(nèi)存,從而創(chuàng)建不出線程。
? Example 6
Out of swap的例子實(shí)在太難舉了,就沒在此列出了,對于Out of swap的OOM,需要首先觀察是否因?yàn)镴ava
Heap設(shè)置太大,導(dǎo)致物理內(nèi)存+swap區(qū)不夠用;如果不是的話,則需關(guān)注到底是哪些地方占用了堆外的內(nèi)存,可通過google-perftools來
跟蹤是哪些代碼在調(diào)用malloc,以及所耗的內(nèi)存比例,在跟蹤到后可繼續(xù)查找原因。
總體來說,Out of swap通常是最難查的OOM,由于其是直接退出java進(jìn)程的,因此需要結(jié)合core
dump文件和hs_err_pid[pid].log進(jìn)行分析,最關(guān)鍵的還是像查java heap那樣,要查出到底是什么代碼造成了native
heap的消耗。
? Example 7
PermGen空間滿造成OOM的情況通常采取的解決方法是簡單的擴(kuò)大PermSize。
總結(jié)上面的例子來看,對于OOM的情況,最重要的是根據(jù)OOM的種類查找到底是代碼中的什么部分造成的消耗。
對于Java Heap Space OOM和GC overhead limit exceeded這兩種類型,可通過heap dump文件以及jmap –histo來進(jìn)行分析,多數(shù)情況下可通過heap dump分析出原因,但也有少數(shù)情況會出現(xiàn)heap dump分析不出原因,而jmap –histo看到某原生類型占據(jù)了大部分的內(nèi)存,這種情況就非常復(fù)雜了,只能是仔細(xì)查看代碼,并捕捉OutOfMemoryError,從而來逐漸定位到 代碼中哪部分拋出的。
對于Out of swap這種類型,其主要是地址空間超過了限制或者對外內(nèi)存不夠用了造成的,首先需要查看Java Heap設(shè)置是否過大,然后可結(jié)合google-perftools來查看到底是哪些代碼調(diào)用了malloc,在堆外分配內(nèi)存。
3 Sun JDK代碼中處理OOM的方式
在Sun JDK代碼中,在不同的OOM時(shí),會調(diào)用不同的處理方式來進(jìn)行處理,下面就來看看JDK中幾個(gè)典型的處理OOM的代碼。
? 創(chuàng)建線程失敗
compiler_thread = new CompilerThread(queue, counters);
if (compiler_thread == NULL || compiler_thread->osthread() == NULL){
vm_exit_during_initialization(“java.lang.OutOfMemoryError”,
“unable to create new native thread”);
}
對于JDK中必須創(chuàng)建成功的線程,如失敗會通過調(diào)用vm_exit_during_initialization打印出OOM錯(cuò)誤,并退出Java進(jìn)程。
對于非必須創(chuàng)建成功的線程,通常會調(diào)用
THROW_MSG(vmSymbols::java_lang_OutOfMemoryError(),
“unable to create new native thread”);
拋出OOM錯(cuò)誤信息。
? 調(diào)用os:malloc失敗
void *p = os::malloc(bytes);
if (p == NULL)
vm_exit_out_of_memory(bytes, “Chunk::new”);
return p;
當(dāng)os:malloc或os:commit_memory失敗時(shí),會直接輸出錯(cuò)誤信息,并退出Java進(jìn)程。
? Java Heap上分配失敗后
report_java_out_of_memory(“Java heap space”);
調(diào)用這個(gè)就表明了不會退出vm,而只是拋出OOM錯(cuò)誤。
例如PermGen分配失敗時(shí)的代碼更為直觀:
HeapWord* result = Universe::heap()->permanent_mem_allocate(size);
if (result != NULL) {
NOT_PRODUCT(Universe::heap()->
check_for_non_bad_heap_word_value(result, size));
assert(!HAS_PENDING_EXCEPTION,
“Unexpected exception, will result in uninitialized storage”);
return result;
}
// -XX:+HeapDumpOnOutOfMemoryError and -XX:OnOutOfMemoryError support
report_java_out_of_memory(“PermGen space”);
總體而言,對于一個(gè)大型系統(tǒng)而言,通常OOM是難以避免的現(xiàn)象,最重要的還是一旦出現(xiàn)OOM,要掌握排查的方法,另外就是,隨著現(xiàn)在內(nèi)存越來越便 宜,CMS GC越來越成熟,采用64 bit操作系統(tǒng),開啟大內(nèi)存也是一種可選方式,基本上可以避免內(nèi)存成為大問題,畢竟在Java中完全可能隨便寫幾行代碼就不知不覺消耗了很多內(nèi)存。
ps: 感興趣的同學(xué)還可參考sun官方的這篇關(guān)于OOM的文章:
http://java.sun.com/javase/6/webnotes/trouble/TSG-VM/html/memleaks.html