CONAN ZONE

          你越掙扎我就越興奮

          BlogJava 首頁 新隨筆 聯(lián)系 聚合 管理
            0 Posts :: 282 Stories :: 0 Comments :: 0 Trackbacks
          轉(zhuǎn)自:http://blog.bluedavy.com/?p=91

          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 bytes for . Out of swap space?
          當(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



          posted on 2010-08-16 18:21 CONAN 閱讀(631) 評論(0)  編輯  收藏 所屬分類: JVM
          主站蜘蛛池模板: 英山县| 景东| 万全县| 荣昌县| 金阳县| 漠河县| 南平市| 渭源县| 庆安县| 乐清市| 绥棱县| 宁海县| 建平县| 通城县| 平江县| 洛宁县| 东辽县| 涞源县| 宜兰市| 邻水| 霍邱县| 台南县| 十堰市| 厦门市| 共和县| 上蔡县| 麻阳| 浦城县| 清原| 射阳县| 子长县| 太谷县| 潮安县| 新乐市| 平阴县| 黔西县| 南部县| 博乐市| 格尔木市| 历史| 南京市|