久久精品精选,精品九九视频,www久久只有这里有精品,亚洲熟女乱色综合一区
    分享

    常見的 OOM 異常分析(硬核干貨)

     鷹兔牛熊眼 2020-07-07

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

    一. 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)

    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 堆內存空間即可。如果仍然沒有解決,可以參考以下情況做進一步處理:

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

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

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

    內存泄露和內存溢出

    內存溢出(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.Cleanerclean() 方法來主動釋放被 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 原因分析

    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 請求的總內存大于可用物理內存的情況下,操作系統開始將內容從內存換出到硬盤驅動器。

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

    這種錯誤沒見過~~~

    九、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 調優。

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

      本站是提供個人知識管理的網絡存儲空間,所有內容均由用戶發布,不代表本站觀點。請注意甄別內容中的聯系方式、誘導購買等信息,謹防詐騙。如發現有害或侵權內容,請點擊一鍵舉報。
      轉藏 分享 獻花(0

      0條評論

      發表

      請遵守用戶 評論公約

      類似文章 更多

      主站蜘蛛池模板: 久久五十路丰满熟女中出| 中文字幕日韩人妻一区| 玩弄丰满少妇人妻视频| 国产精品丝袜亚洲熟女| 免费久久人人爽人人爽AV| jizz视频在线观看| 久久久久成人片免费观看蜜芽| 欧美人妻一区二区三区| 久久99热只有频精品8| 国产精品久久久天天影视香蕉| 2021亚洲国产精品无码| 国产在线高清视频无码| 色一情一乱一伦麻豆| 成 人色 网 站 欧美大片| 欧美交a欧美精品喷水| 99精品国产一区二区电影| 中文字幕精品无码一区二区三区| XXXXXHD亚洲日本HD| 国产精品白丝喷水在线观看| 亚洲色大成成人网站久久| 亚洲尤码不卡av麻豆| 日产精品一卡2卡三卡四乱码| 亚洲av成人无码精品电影在线| 强奷漂亮少妇高潮麻豆| 亚洲天堂av日韩精品| 日韩AV片无码一区二区不卡电影 | 男人添女人下部高潮视频| 国产亚洲精品中文字幕| 99久久免费精品国产72精品九九| 国产欧美综合在线观看第十页 | 老熟妇性色老熟妇性| 四川丰满少妇A级毛片| 亚洲AV永久无码精品主页| 欧美饥渴熟妇高潮喷水| 亚洲国内精品一区二区| WWW夜片内射视频在观看视频| 成人片黄网站色大片免费观看| 中文字幕在线精品人妻| 亚洲精品成人片在线观看精品字幕 | 久久五十路丰满熟女中出| 日本一区不卡高清更新二区|