日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

教你寫Bug,常見的 OOM 異常分析

 

在《JAVA虛擬機規范》的規定里,除了程序計數器外,虛擬機內存的其他幾個運行時區域都有發生 OutOfMemoryError 異常的可能。

本篇主要包括如下 OOM 的介紹和示例:

  • java.lang.StackOverflowError
  • java.lang.OutOfMemoryError: Java heap space
  • java.lang.OutOfMemoryError: GC overhead limit exceeded
  • java.lang.OutOfMemoryError-->Metaspace
  • java.lang.OutOfMemoryError: Direct buffer memory
  • java.lang.OutOfMemoryError: unable to create new native thread
  • java.lang.OutOfMemoryError:Metaspace
  • java.lang.OutOfMemoryError: Requested array size exceeds VM limit
  • java.lang.OutOfMemoryError: Out of swap space
  • java.lang.OutOfMemoryError:Kill process or sacrifice child

我們常說的 OOM 異常,其實是 Error

教你寫Bug,常見的 OOM 異常分析

 

一. StackOverflowError

1.1 寫個 bug

public class StackOverflowErrorDemo {
    public static void main(String[] args) {
        javaKeeper();
    }
    private static void javaKeeper() {
        javaKeeper();
    }
}

上一篇詳細的介紹過

JVM 運行時數據區

,JVM 虛擬機棧是有深度的,在執行方法的時候會伴隨著入棧和出棧,上邊的方法可以看到,main 方法執行后不停的遞歸,遲早把棧撐爆了

Exception in thread "main" java.lang.StackOverflowError
 at oom.StackOverflowErrorDemo.javaKeeper(StackOverflowErrorDemo.java:15)
教你寫Bug,常見的 OOM 異常分析

 

1.2 原因分析

  • 無限遞歸循環調用(最常見原因),要時刻注意代碼中是否有了循環調用方法而無法退出的情況
  • 執行了大量方法,導致線程棧空間耗盡
  • 方法內聲明了海量的局部變量
  • native 代碼有棧上分配的邏輯,并且要求的內存還不小,比如 java.net.SocketInputStream.read0 會在棧上要求分配一個 64KB 的緩存(64位 linux)

1.3 解決方案

  • 修復引發無限遞歸調用的異常代碼, 通過程序拋出的異常堆棧,找出不斷重復的代碼行,按圖索驥,修復無限遞歸 Bug
  • 排查是否存在類之間的循環依賴(當兩個對象相互引用,在調用toString方法時也會產生這個異常)
  • 通過 JVM 啟動參數 -Xss 增加線程棧內存空間, 某些正常使用場景需要執行大量方法或包含大量局部變量,這時可以適當地提高線程棧空間限制

二. Java heap space

Java 堆用于存儲對象實例,我們只要不斷的創建對象,并且保證 GC Roots 到對象之間有可達路徑來避免 GC 清除這些對象,那隨著對象數量的增加,總容量觸及堆的最大容量限制后就會產生內存溢出異常。

Java 堆內存的 OOM 異常是實際應用中最常見的內存溢出異常。

2.1 寫個 bug

/**
 * JVM參數:-Xmx12m
 */
public class JavaHeapSpaceDemo {
    static final int SIZE = 2 * 1024 * 1024;
    public static void main(String[] a) {
        int[] i = new int[SIZE];
    }
}

代碼試圖分配容量為 2M 的 int 數組,如果指定啟動參數 -Xmx12m,分配內存就不夠用,就類似于將 XXXL 號的對象,往 S 號的 Java heap space 里面塞。

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
 at oom.JavaHeapSpaceDemo.main(JavaHeapSpaceDemo.java:13)

2.2 原因分析

  • 請求創建一個超大對象,通常是一個大數組
  • 超出預期的訪問量/數據量,通常是上游系統請求流量飆升,常見于各類促銷/秒殺活動,可以結合業務流量指標排查是否有尖狀峰值
  • 過度使用終結器(Finalizer),該對象沒有立即被 GC
  • 內存泄漏(Memory Leak),大量對象引用沒有釋放,JVM 無法對其自動回收,常見于使用了 File 等資源沒有回收

2.3 解決方案

針對大部分情況,通常只需要通過 -Xmx 參數調高 JVM 堆內存空間即可。如果仍然沒有解決,可以參考以下情況做進一步處理:

  • 如果是超大對象,可以檢查其合理性,比如是否一次性查詢了數據庫全部結果,而沒有做結果數限制
  • 如果是業務峰值壓力,可以考慮添加機器資源,或者做限流降級。
  • 如果是內存泄漏,需要找到持有的對象,修改代碼設計,比如關閉沒有釋放的連接
教你寫Bug,常見的 OOM 異常分析

 

面試官:說說內存泄露和內存溢出

加送個知識點,三連的終將成為大神~~

內存泄露和內存溢出

內存溢出(out of memory),是指程序在申請內存時,沒有足夠的內存空間供其使用,出現out of memory;比如申請了一個 Integer,但給它存了 Long 才能存下的數,那就是內存溢出。

內存泄露( memory leak),是指程序在申請內存后,無法釋放已申請的內存空間,一次內存泄露危害可以忽略,但內存泄露堆積后果很嚴重,無論多少內存,遲早會被占光。

memory leak 最終會導致 out of memory!

三、GC overhead limit exceeded

JVM 內置了垃圾回收機制GC,所以作為 Javaer 的我們不需要手工編寫代碼來進行內存分配和釋放,但是當 Java 進程花費 98% 以上的時間執行 GC,但只恢復了不到 2% 的內存,且該動作連續重復了 5 次,就會拋出 java.lang.OutOfMemoryError:GC overhead limit exceeded 錯誤(俗稱:垃圾回收上頭)。簡單地說,就是應用程序已經基本耗盡了所有可用內存, GC 也無法回收。

假如不拋出 GC overhead limit exceeded 錯誤,那 GC 清理的那么一丟丟內存很快就會被再次填滿,迫使 GC 再次執行,這樣惡性循環,CPU 使用率 100%,而 GC 沒什么效果。

3.1 寫個 bug

出現這個錯誤的實例,其實我們寫個無限循環,往 List 或 Map 加數據就會一直 Full GC,直到扛不住,這里用一個不容易發現的栗子。我們往 map 中添加 1000 個元素。

/**
 * JVM 參數: -Xmx14m -XX:+PrintGCDetails
 */
public class KeylessEntry {
    static class Key {
        Integer id;
        Key(Integer id) {
            this.id = id;
        }
        @Override
        public int hashCode() {
            return id.hashCode();
        }
    }
    public static void main(String[] args) {
        Map m = new HashMap();
        while (true){
            for (int i = 0; i < 1000; i++){
                if (!m.containsKey(new Key(i))){
                    m.put(new Key(i), "Number:" + i);
                }
            }
            System.out.println("m.size()=" + m.size());
        }
    }
}
...
m.size()=54000
m.size()=55000
m.size()=56000
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded

從輸出結果可以看到,我們的限制 1000 條數據沒有起作用,map 容量遠超過了 1000,而且最后也出現了我們想要的錯誤,這是因為類 Key 只重寫了 hashCode() 方法,卻沒有重寫 equals() 方法,我們在使用 containsKey() 方法其實就出現了問題,于是就會一直往 HashMap 中添加 Key,直至 GC 都清理不掉。

‍ 面試官又來了:說一下HashMap原理以及為什么需要同時實現equals和hashcode

執行這個程序的最終錯誤,和 JVM 配置也會有關系,如果設置的堆內存特別小,會直接報 Java heap space。算是被這個錯誤截胡了,所以有時,在資源受限的情況下,無法準確預測程序會死于哪種具體的原因。

3.2 解決方案

  • 添加 JVM 參數-XX:-UseGCOverheadLimit 不推薦這么干,沒有真正解決問題,只是將異常推遲
  • 檢查項目中是否有大量的死循環或有使用大內存的代碼,優化代碼
  • dump內存分析,檢查是否存在內存泄露,如果沒有,加大內存

四、Direct buffer memory

我們使用 NIO 的時候經常需要使用 ByteBuffer 來讀取或寫入數據,這是一種基于 Channel(通道) 和 Buffer(緩沖區)的 I/O 方式,它可以使用 Native 函數庫直接分配堆外內存,然后通過一個存儲在 Java 堆里面的 DirectByteBuffer 對象作為這塊內存的引用進行操作。這樣在一些場景就避免了 Java 堆和 Native 中來回復制數據,所以性能會有所提高。

Java 允許應用程序通過 Direct ByteBuffer 直接訪問堆外內存,許多高性能程序通過 Direct ByteBuffer 結合內存映射文件(Memory MApped File)實現高速 IO。

4.1 寫個 bug

  • ByteBuffer.allocate(capability) 是分配 JVM 堆內存,屬于 GC 管轄范圍,需要內存拷貝所以速度相對較慢;
  • ByteBuffer.allocateDirect(capability) 是分配 OS 本地內存,不屬于 GC 管轄范圍,由于不需要內存拷貝所以速度相對較快;

如果不斷分配本地內存,堆內存很少使用,那么 JVM 就不需要執行 GC,DirectByteBuffer 對象就不會被回收,這時雖然堆內存充足,但本地內存可能已經不夠用了,就會出現 OOM,本地直接內存溢出

/**
 *  VM Options:-Xms10m,-Xmx10m,-XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
 */
public class DirectBufferMemoryDemo {
    public static void main(String[] args) {
        System.out.println("maxDirectMemory is:"+sun.misc.VM.maxDirectMemory() / 1024 / 1024 + "MB");
        //ByteBuffer buffer = ByteBuffer.allocate(6*1024*1024);
        ByteBuffer buffer = ByteBuffer.allocateDirect(6*1024*1024);
    }
}

最大直接內存,默認是電腦內存的 1/4,所以我們設小點,然后使用直接內存超過這個值,就會出現 OOM。

maxDirectMemory is:5MB
Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory

4.2 解決方案

  1. Java 只能通過 ByteBuffer.allocateDirect 方法使用 Direct ByteBuffer,因此,可以通過 Arthas 等在線診斷工具攔截該方法進行排查
  2. 檢查是否直接或間接使用了 NIO,如 netty,jetty 等
  3. 通過啟動參數 -XX:MaxDirectMemorySize 調整 Direct ByteBuffer 的上限值
  4. 檢查 JVM 參數是否有 -XX:+DisableExplicitGC 選項,如果有就去掉,因為該參數會使 System.gc() 失效
  5. 檢查堆外內存使用代碼,確認是否存在內存泄漏;或者通過反射調用 sun.misc.Cleaner 的 clean() 方法來主動釋放被 Direct ByteBuffer 持有的內存空間
  6. 內存容量確實不足,升級配置

五、Unable to create new native thread

每個 Java 線程都需要占用一定的內存空間,當 JVM 向底層操作系統請求創建一個新的 native 線程時,如果沒有足夠的資源分配就會報此類錯誤。

5.1 寫個 bug

public static void main(String[] args) {
  while(true){
    new Thread(() -> {
      try {
        Thread.sleep(Integer.MAX_VALUE);
      } catch(InterruptedException e) { }
    }).start();
  }
}
Error occurred during initialization of VM
java.lang.OutOfMemoryError: unable to create new native thread

5.2 原因分析

教你寫Bug,常見的 OOM 異常分析

 

JVM 向 OS 請求創建 native 線程失敗,就會拋出 Unableto createnewnativethread,常見的原因包括以下幾類:

  • 線程數超過操作系統最大線程數限制(和平臺有關)
  • 線程數超過 kernel.pid_max(只能重啟)
  • native 內存不足;該問題發生的常見過程主要包括以下幾步:
  1. JVM 內部的應用程序請求創建一個新的 Java 線程;
  2. JVM native 方法代理了該次請求,并向操作系統請求創建一個 native 線程;
  3. 操作系統嘗試創建一個新的 native 線程,并為其分配內存;
  4. 如果操作系統的虛擬內存已耗盡,或是受到 32 位進程的地址空間限制,操作系統就會拒絕本次 native 內存分配;
  5. JVM 將拋出 java.lang.OutOfMemoryError:Unableto createnewnativethread 錯誤。

5.3 解決方案

  1. 想辦法降低程序中創建線程的數量,分析應用是否真的需要創建這么多線程
  2. 如果確實需要創建很多線程,調高 OS 層面的線程最大數:執行 ulimia-a 查看最大線程數限制,使用 ulimit-u xxx 調整最大線程數限制

六、Metaspace

JDK 1.8 之前會出現 Permgen space,該錯誤表示永久代(Permanent Generation)已用滿,通常是因為加載的 class 數目太多或體積太大。隨著 1.8 中永久代的取消,就不會出現這種異常了。

Metaspace 是方法區在 HotSpot 中的實現,它與永久代最大的區別在于,元空間并不在虛擬機內存中而是使用本地內存,但是本地內存也有打滿的時候,所以也會有異常。

6.1 寫個 bug

/**
 * JVM Options: -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
 */
public class MetaspaceOOMDemo {
    public static void main(String[] args) {
        while (true) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(MetaspaceOOMDemo.class);
            enhancer.setUseCache(false);
            enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {
                //動態代理創建對象
                return methodProxy.invokeSuper(o, objects);
            });
            enhancer.create();
        }
    }
}

借助 Spring 的 GCLib 實現動態創建對象

Exception in thread "main" org.springframework.cglib.core.CodeGenerationException: java.lang.OutOfMemoryError-->Metaspace

6.2 解決方案

方法區溢出也是一種常見的內存溢出異常,在經常運行時生成大量動態類的應用場景中,就應該特別關注這些類的回收情況。這類場景除了上邊的 GCLib 字節碼增強和動態語言外,常見的還有,大量 JSP 或動態產生 JSP 文件的應用(遠古時代的傳統軟件行業可能會有)、基于 OSGi 的應用(即使同一個類文件,被不同的加載器加載也會視為不同的類)等。

方法區在 JDK8 中一般不太容易產生,HotSpot 提供了一些參數來設置元空間,可以起到預防作用

  • -XX:MaxMetaspaceSize 設置元空間最大值,默認是 -1,表示不限制(還是要受本地內存大小限制的)
  • -XX:MetaspaceSize 指定元空間的初始空間大小,以字節為單位,達到該值就會觸發 GC 進行類型卸載,同時收集器會對該值進行調整
  • -XX:MinMetaspaceFreeRatio 在 GC 之后控制最小的元空間剩余容量的百分比,可減少因元空間不足導致的垃圾收集頻率,類似的還有 MaxMetaspaceFreeRatio

七、Requested array size exceeds VM limit

7.1 寫個 bug

public static void main(String[] args) {
  int[] arr = new int[Integer.MAX_VALUE];
}

這個比較簡單,建個超級大數組就會出現 OOM,不多說了

Exception in thread "main" java.lang.OutOfMemoryError: Requested array size exceeds VM limit

JVM 限制了數組的最大長度,該錯誤表示程序請求創建的數組超過最大長度限制。

JVM 在為數組分配內存前,會檢查要分配的數據結構在系統中是否可尋址,通常為 Integer.MAX_VALUE-2。

此類問題比較罕見,通常需要檢查代碼,確認業務是否需要創建如此大的數組,是否可以拆分為多個塊,分批執行。

八、Out of swap space

啟動 Java 應用程序會分配有限的內存。此限制是通過-Xmx和其他類似的啟動參數指定的。

在 JVM 請求的總內存大于可用物理內存的情況下,操作系統開始將內容從內存換出到硬盤驅動器。

教你寫Bug,常見的 OOM 異常分析

 

該錯誤表示所有可用的虛擬內存已被耗盡。虛擬內存(Virtual Memory)由物理內存(Physical Memory)和交換空間(Swap Space)兩部分組成。

這種錯誤沒見過~~~

教你寫Bug,常見的 OOM 異常分析

 

九、Kill process or sacrifice child

操作系統是建立在流程概念之上的。這些進程由幾個內核作業負責,其中一個名為“ Out of memory Killer”,它會在可用內存極低的情況下“殺死”(kill)某些進程。OOM Killer 會對所有進程進行打分,然后將評分較低的進程“殺死”,具體的評分規則可以參考 Surviving the Linux OOM Killer。

不同于其他的 OOM 錯誤, Killprocessorsacrifice child 錯誤不是由 JVM 層面觸發的,而是由操作系統層面觸發的。

9.1 原因分析

默認情況下,Linux 內核允許進程申請的內存總量大于系統可用內存,通過這種“錯峰復用”的方式可以更有效的利用系統資源。

然而,這種方式也會無可避免地帶來一定的“超賣”風險。例如某些進程持續占用系統內存,然后導致其他進程沒有可用內存。此時,系統將自動激活 OOM Killer,尋找評分低的進程,并將其“殺死”,釋放內存資源。

9.2 解決方案

  • 升級服務器配置/隔離部署,避免爭用
  • OOM Killer 調優。

最后附上一張“涯海”大神的圖

教你寫Bug,常見的 OOM 異常分析

 

涯海

參考與感謝

《深入理解 Java 虛擬機 第 3 版》

https://plumbr.io/outofmemoryerror

https://yq.aliyun.com/articles/711191

https://github.com/StabilityMan/StabilityGuide/blob/master/docs/diagnosis/jvm/exception

分享到:
標簽:異常 OOM
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定