<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Java程序員必備:常見(jiàn)OOM異常分析

          共 5483字,需瀏覽 11分鐘

           ·

          2020-01-21 23:24

          前言

          放假這幾天,溫習(xí)了深入理解Java虛擬機(jī)的第二章, 整理了JVM發(fā)生OOM異常的幾種情況,并分析原因以及解決方案,希望對(duì)大家有幫助。

          ??

          Java 堆溢出

          Java堆用于存儲(chǔ)對(duì)象實(shí)例,只要不斷地創(chuàng)建對(duì)象,并且保證GC Roots到對(duì)象之間有可達(dá)路徑來(lái)避免垃圾回收機(jī)制清除這些對(duì)象,那么在對(duì)象數(shù)量到達(dá)最大堆的容量限制后就會(huì)產(chǎn)生內(nèi)存溢出異常。

          Java 堆溢出原因

          • 無(wú)法在 Java 堆中分配對(duì)象

          • 應(yīng)用程序保存了無(wú)法被GC回收的對(duì)象。

          • 應(yīng)用程序過(guò)度使用 finalizer。

          Java 堆溢出排查解決思路

          1.查找關(guān)鍵報(bào)錯(cuò)信息,如

          1. java.lang.OutOfMemoryError:Java heap space

          2.使用內(nèi)存映像分析工具(如Eclipsc Memory Analyzer或者Jprofiler)對(duì)Dump出來(lái)的堆儲(chǔ)存快照進(jìn)行分析,分析清楚是內(nèi)存泄漏還是內(nèi)存溢出。

          3.如果是內(nèi)存泄漏,可進(jìn)一步通過(guò)工具查看泄漏對(duì)象到GC Roots的引用鏈,修復(fù)應(yīng)用程序中的內(nèi)存泄漏。

          4.如果不存在泄漏,先檢查代碼是否有死循環(huán),遞歸等,再考慮用 -Xmx 增加堆大小。

          demo代碼

          1. package oom;


          2. import java.util.ArrayList;

          3. import java.util.List;


          4. /**

          5. * JVM配置參數(shù)

          6. * -Xms20m JVM初始分配的內(nèi)存20m

          7. * -Xmx20m JVM最大可用內(nèi)存為20m

          8. * -XX:+HeapDumpOnOutOfMemoryError 當(dāng)JVM發(fā)生OOM時(shí),自動(dòng)生成DUMP文件

          9. * -XX:HeapDumpPath=/Users/weihuaxiao/Desktop/dump/ 生成DUMP文件的路徑

          10. */

          11. publicclassHeapOOM{

          12. staticclassOOMObject{

          13. }

          14. publicstaticvoid main(String[] args){

          15. List<OOMObject> list =newArrayList<OOMObject>();

          16. //在堆中無(wú)限創(chuàng)建對(duì)象

          17. while(true){

          18. list.add(newOOMObject());

          19. }

          20. }

          21. }

          運(yùn)行結(jié)果

          31e144882f56d2a0b75e8b2e522a254a.webp

          按照前面的排查解決方案,我們來(lái)一波分析。

          1.查找報(bào)錯(cuò)關(guān)鍵信息

          1. Exceptionin thread "main" java.lang.OutOfMemoryError:Java heap space

          2. 使用內(nèi)存映像分析工具Jprofiler分析產(chǎn)生的堆儲(chǔ)存快照

          cee14daa85e7adf2654c976b6ee661de.webp

          由圖可得,OOMObject這個(gè)類創(chuàng)建了810326個(gè)實(shí)例,是屬于內(nèi)存溢出,這時(shí)候先定位到對(duì)應(yīng)代碼,發(fā)現(xiàn)死循環(huán)導(dǎo)致的,修復(fù)即可。


          棧溢出

          關(guān)于虛擬機(jī)棧和本地方法棧,在Java虛擬機(jī)規(guī)范中描述了兩種異常:

          • 如果線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的深度,將拋出StackOverflowError 異常;

          • 如果虛擬機(jī)棧可以動(dòng)態(tài)擴(kuò)展,當(dāng)擴(kuò)展時(shí)無(wú)法申請(qǐng)到足夠的內(nèi)存時(shí)會(huì)拋出 OutOfMemoryError 異常。

          棧溢出原因

          • 在單個(gè)線程下,棧幀太大,或者虛擬機(jī)棧容量太小,當(dāng)內(nèi)存無(wú)法分配的時(shí)候,虛擬機(jī)拋出StackOverflowError 異常。

          • 不斷地建立線程的方式會(huì)導(dǎo)致內(nèi)存溢出。

          棧溢出排查解決思路

          1. 查找關(guān)鍵報(bào)錯(cuò)信息,確定是StackOverflowError還是OutOfMemoryError

          2. 如果是StackOverflowError,檢查代碼是否遞歸調(diào)用方法等

          3. 如果是OutOfMemoryError,檢查是否有死循環(huán)創(chuàng)建線程等,通過(guò)-Xss降低的每個(gè)線程棧大小的容量

          demo代碼

          1. package oom;


          2. /**

          3. * -Xss2M

          4. */

          5. publicclassJavaVMStackOOM{

          6. privatevoid dontStop(){

          7. while(true){


          8. }

          9. }

          10. publicvoid stackLeakByThread(){

          11. while(true){

          12. Thread thread =newThread(newRunnable(){

          13. publicvoid run(){

          14. dontStop();

          15. }

          16. });

          17. thread.start();}

          18. }

          19. publicstaticvoid main(String[] args){

          20. JavaVMStackOOM oom =newJavaVMStackOOM();

          21. oom.stackLeakByThread();

          22. }

          23. }

          運(yùn)行結(jié)果

          3dfc057c836ba67592403a0759d94794.webp

          1.查找報(bào)錯(cuò)關(guān)鍵信息

          1. Exceptionin thread "main" java.lang.OutOfMemoryError: unable to create newnative thread

          2.確定是創(chuàng)建線程導(dǎo)致的棧溢出OOM

          1. Thread thread =newThread(newRunnable(){

          2. publicvoid run(){

          3. dontStop();

          4. }

          5. });

          3.排查代碼,確定是否顯示使用死循環(huán)創(chuàng)建線程,或者隱式調(diào)用第三方接口創(chuàng)建線程(之前公司,調(diào)用騰訊云第三方接口,上傳圖片,遇到這個(gè)問(wèn)題)


          方法區(qū)溢出

          方法區(qū),(又叫永久代,JDK8后,元空間替換了永久代),用于存放Class的相關(guān)信息,如類名、訪問(wèn)修飾符、常量池、字段描述、方法描述等。運(yùn)行時(shí)產(chǎn)生大量的類,會(huì)填滿方法區(qū),造成溢出。

          方法區(qū)溢出原因

          • 使用CGLib生成了大量的代理類,導(dǎo)致方法區(qū)被撐爆

          • 在Java7之前,頻繁的錯(cuò)誤使用String.intern方法

          • 大量jsp和動(dòng)態(tài)產(chǎn)生jsp

          • 應(yīng)用長(zhǎng)時(shí)間運(yùn)行,沒(méi)有重啟

          方法區(qū)溢出排查解決思路

          • 檢查是否永久代空間設(shè)置得過(guò)小

          • 檢查代碼是否頻繁錯(cuò)誤得使用String.intern方法

          • 檢查是否跟jsp有關(guān)。

          • 檢查是否使用CGLib生成了大量的代理類

          • 重啟大法,重啟JVM

          demo代碼

          1. package oom;


          2. import org.springframework.cglib.proxy.Enhancer;

          3. import org.springframework.cglib.proxy.MethodInterceptor;

          4. import org.springframework.cglib.proxy.MethodProxy;


          5. import java.lang.reflect.Method;


          6. /**

          7. * jdk8以上的話,

          8. * 虛擬機(jī)參數(shù):-XX:MetaspaceSize=10M -XX:MaxMetaspaceSize=10M

          9. */

          10. publicclassJavaMethodAreaOOM{

          11. publicstaticvoid main(String[] args){

          12. while(true){

          13. Enhancer enhancer =newEnhancer();

          14. enhancer.setSuperclass(OOMObject.class);

          15. enhancer.setUseCache(false);

          16. enhancer.setCallback(newMethodInterceptor(){

          17. publicObject intercept(Object obj,Method method,

          18. Object[] args,MethodProxy proxy)throwsThrowable{

          19. return proxy.invokeSuper(obj, args);

          20. }

          21. });

          22. enhancer.create();

          23. }

          24. }

          25. staticclassOOMObject{

          26. }

          27. }

          運(yùn)行結(jié)果

          57c92ba7fa111f39657048bbfa64e18b.webp

          1.查找報(bào)錯(cuò)關(guān)鍵信息

          1. Causedby: java.lang.OutOfMemoryError:Metaspace

          2.檢查JVM元空間設(shè)置參數(shù)是否過(guò)小

          1. -XX:MetaspaceSize=10M-XX:MaxMetaspaceSize=10M

          3. 檢查對(duì)應(yīng)代碼,是否使用CGLib生成了大量的代理類

          1. while(true){

          2. ...

          3. enhancer.setCallback(newMethodInterceptor(){

          4. publicObject intercept(Object obj,Method method,

          5. Object[] args,MethodProxy proxy)throwsThrowable{

          6. return proxy.invokeSuper(obj, args);

          7. }

          8. });

          9. enhancer.create();

          10. }


          本機(jī)直接內(nèi)存溢出

          直接內(nèi)存并不是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分,也不是Java 虛擬機(jī)規(guī)范中定義的內(nèi)存區(qū)域。但是,這部分內(nèi)存也被頻繁地使用,而且也可能導(dǎo)致OOM。

          在JDK1.4 中新加入了NIO(New Input/Output)類,它可以使用 native 函數(shù)庫(kù)直接分配堆外內(nèi)存,然后通過(guò)一個(gè)存儲(chǔ)在Java堆中的 DirectByteBuffer 對(duì)象作為這塊內(nèi)存的引用進(jìn)行操作。這樣能在一些場(chǎng)景中顯著提高性能,因?yàn)楸苊饬嗽?Java 堆和 Native 堆中來(lái)回復(fù)制數(shù)據(jù)。

          直接內(nèi)存溢出原因

          • 本機(jī)直接內(nèi)存的分配雖然不會(huì)受到Java 堆大小的限制,但是受到本機(jī)總內(nèi)存大小限制。

          • 直接內(nèi)存由 -XX:MaxDirectMemorySize 指定,如果不指定,則默認(rèn)與Java堆最大值(-Xmx指定)一樣。

          • NIO程序中,使用ByteBuffer.allocteDirect(capability)分配的是直接內(nèi)存,可能導(dǎo)致直接內(nèi)存溢出。

          直接內(nèi)存溢出

          • 檢查代碼是否恰當(dāng)

          • 檢查JVM參數(shù)-Xmx,-XX:MaxDirectMemorySize 是否合理。

          demo代碼

          1. package oom;


          2. import java.nio.ByteBuffer;

          3. import java.util.concurrent.TimeUnit;


          4. /**

          5. * -Xmx256m -XX:MaxDirectMemorySize=100M

          6. */

          7. publicclassDirectByteBufferTest{

          8. publicstaticvoid main(String[] args)throwsInterruptedException{

          9. //分配128MB直接內(nèi)存

          10. ByteBuffer bb =ByteBuffer.allocateDirect(1024*1024*128);


          11. TimeUnit.SECONDS.sleep(10);

          12. System.out.println("ok");

          13. }

          14. }

          運(yùn)行結(jié)果

          1d90f8cad23ca7cc6c654a7f1d218ab9.webp

          ByteBuffer分配128MB直接內(nèi)存,而JVM參數(shù)-XX:MaxDirectMemorySize=100M指定最大是100M,因此發(fā)生直接內(nèi)存溢出。

          1. ByteBuffer bb =ByteBuffer.allocateDirect(1024*1024*128);

          ??

          GC overhead limit exceeded

          • 這個(gè)是JDK6新加的錯(cuò)誤類型,一般都是堆太小導(dǎo)致的。

          • Sun 官方對(duì)此的定義:超過(guò)98%的時(shí)間用來(lái)做GC并且回收了不到2%的堆內(nèi)存時(shí)會(huì)拋出此異常。

          解決方案

          • 檢查項(xiàng)目中是否有大量的死循環(huán)或有使用大內(nèi)存的代碼,優(yōu)化代碼。

          • 檢查JVM參數(shù)-Xmx -Xms是否合理

          • dump內(nèi)存,檢查是否存在內(nèi)存泄露,如果沒(méi)有,加大內(nèi)存。

          demo代碼

          1. package oom;


          2. import java.util.concurrent.ExecutorService;

          3. import java.util.concurrent.Executors;


          4. /**

          5. * JVm參數(shù) -Xmx8m -Xms8m

          6. */

          7. publicclassGCoverheadTest{

          8. publicstaticvoid main(String[] args){

          9. ExecutorService executor =Executors.newFixedThreadPool(10);

          10. for(int i =0; i <Integer.MAX_VALUE; i++){

          11. executor.execute(()->{

          12. try{

          13. Thread.sleep(10000);

          14. }catch(InterruptedException e){

          15. //do nothing

          16. }

          17. });

          18. }

          19. }

          20. }

          運(yùn)行結(jié)果

          e28285b175e11d1369890c14a82433b8.webp實(shí)例代碼使用了newFixedThreadPool線程池,它使用了無(wú)界隊(duì)列,無(wú)限循環(huán)執(zhí)行任務(wù),會(huì)導(dǎo)致內(nèi)存飆升。因?yàn)樵O(shè)置了堆比較小,所以出現(xiàn)此類型OOM。

          ??

          總結(jié)

          本文介紹了以下幾種常見(jiàn)OOM異常

          1. java.lang.OutOfMemoryError:Java heap space

          2. java.lang.OutOfMemoryError: unable to create newnative thread

          3. java.lang.OutOfMemoryError:Metaspace

          4. java.lang.OutOfMemoryError:Direct buffer memory

          5. java.lang.OutOfMemoryError: GC overhead limit exceeded

          希望大家遇到OOM異常時(shí),對(duì)癥下藥,順利解決問(wèn)題。同時(shí),如果有哪里寫(xiě)得不對(duì),歡迎指出,感激不盡。?

          參考與感謝

          • JVM系列之實(shí)戰(zhàn)內(nèi)存溢出異常

          • JVM 發(fā)生 OOM 的 8 種原因、及解決辦法

          • NIO-直接內(nèi)存

          • 《深入理解Java虛擬機(jī)》


          有道無(wú)術(shù),術(shù)可成;有術(shù)無(wú)道,止于術(shù)

          歡迎大家關(guān)注Java之道公眾號(hào)


          好文章,我在看??

          瀏覽 92
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产日日日 | 做aAAAAA免费视频 | 久热大香蕉 | 激情开心五月天 | 大香蕉国产在线观看 |