<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>

          常見OOM異常分析

          共 6724字,需瀏覽 14分鐘

           ·

          2021-04-30 14:16

          走過(guò)路過(guò)不要錯(cuò)過(guò)

          點(diǎn)擊藍(lán)字關(guān)注我們


          前言

          放假這幾天,溫習(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ò)信息,如


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

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

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

          demo代碼

          package oom;
          import java.util.ArrayList;import java.util.List;
          /** * JVM配置參數(shù) * -Xms20m JVM初始分配的內(nèi)存20m * -Xmx20m JVM最大可用內(nèi)存為20m * -XX:+HeapDumpOnOutOfMemoryError 當(dāng)JVM發(fā)生OOM時(shí),自動(dòng)生成DUMP文件 * -XX:HeapDumpPath=/Users/weihuaxiao/Desktop/dump/ 生成DUMP文件的路徑 */public class HeapOOM { static class OOMObject { } public static void main(String[] args) { List<OOMObject> list = new ArrayList<OOMObject>(); //在堆中無(wú)限創(chuàng)建對(duì)象 while (true) { list.add(new OOMObject()); } }}

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

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

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

          Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

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




          由圖可得,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代碼

          package oom;
          /** * -Xss2M */public class JavaVMStackOOM { private void dontStop(){ while(true){
          } } public void stackLeakByThread(){ while(true){ Thread thread = new Thread(new Runnable(){ public void run() { dontStop(); } }); thread.start();} } public static void main(String[] args) { JavaVMStackOOM oom = new JavaVMStackOOM(); oom.stackLeakByThread(); }}

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

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

          Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread

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

            Thread thread = new Thread(new Runnable(){                public void run() {                    dontStop();                }            });

          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)行,沒有重啟

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

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

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

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

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

          • 重啟大法,重啟JVM

          demo代碼

          package oom;
          import org.springframework.cglib.proxy.Enhancer;import org.springframework.cglib.proxy.MethodInterceptor;import org.springframework.cglib.proxy.MethodProxy;
          import java.lang.reflect.Method;
          /** * jdk8以上的話, * 虛擬機(jī)參數(shù):-XX:MetaspaceSize=10M -XX:MaxMetaspaceSize=10M */public class JavaMethodAreaOOM { public static void main(String[] args) { while (true) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(OOMObject.class); enhancer.setUseCache(false); enhancer.setCallback(new MethodInterceptor() { public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { return proxy.invokeSuper(obj, args); } }); enhancer.create(); } } static class OOMObject { }}

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

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

          Caused by: java.lang.OutOfMemoryError: Metaspace

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

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

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

            while (true) {  ...   enhancer.setCallback(new MethodInterceptor() {                public Object intercept(Object obj, Method method,                                        Object[] args, MethodProxy proxy) throws Throwable {                    return proxy.invokeSuper(obj, args);                }            });            enhancer.create();  }

          本機(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代碼

          package oom;
          import java.nio.ByteBuffer;import java.util.concurrent.TimeUnit;
          /** * -Xmx256m -XX:MaxDirectMemorySize=100M */public class DirectByteBufferTest { public static void main(String[] args) throws InterruptedException{ //分配128MB直接內(nèi)存 ByteBuffer bb = ByteBuffer.allocateDirect(1024*1024*128);
          TimeUnit.SECONDS.sleep(10); System.out.println("ok"); }}

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

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

           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)存泄露,如果沒有,加大內(nèi)存。

          demo代碼

          package oom;
          import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;
          /** * JVm參數(shù) -Xmx8m -Xms8m */public class GCoverheadTest { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(10); for (int i = 0; i < Integer.MAX_VALUE; i++) { executor.execute(() -> { try { Thread.sleep(10000); } catch (InterruptedException e) { //do nothing } }); } }}

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

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

          總結(jié)

          本文介紹了以下幾種常見OOM異常

          java.lang.OutOfMemoryError: Java heap spacejava.lang.OutOfMemoryError: unable to create new native threadjava.lang.OutOfMemoryError: Metaspacejava.lang.OutOfMemoryError: Direct buffer memoryjava.lang.OutOfMemoryError: GC overhead limit exceeded

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





          往期精彩推薦



          騰訊、阿里、滴滴后臺(tái)面試題匯總總結(jié) — (含答案)

          面試:史上最全多線程面試題 !

          最新阿里內(nèi)推Java后端面試題

          JVM難學(xué)?那是因?yàn)槟銢]認(rèn)真看完這篇文章


          END


          關(guān)注作者微信公眾號(hào) —《JAVA爛豬皮》


          了解更多java后端架構(gòu)知識(shí)以及最新面試寶典


          你點(diǎn)的每個(gè)好看,我都認(rèn)真當(dāng)成了


          看完本文記得給作者點(diǎn)贊+在看哦~~~大家的支持,是作者源源不斷出文的動(dòng)力

          瀏覽 67
          點(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>
                  日韩AV电影网站 | 中国美女操逼网站 | 精品免费囯产一区二区三区四区的使用方法 | 欧美成人手机视频 | 伊人成综合网 |