模擬實戰排查堆記憶體溢位(java.lang.OutOfMemoryError: Java heap space)問題

木子雷發表於2020-05-29

前言:

模擬實戰中排查堆記憶體溢位(java.lang.OutOfMemoryError: Java heap space)的問題。

堆記憶體溢位的原因:一般都是建立了大量的物件,這些物件一直被引用著,無法被GC垃圾回收掉,最終導致堆記憶體被佔滿,沒有足夠的空間存放新建立的物件時,就會出現堆記憶體溢位問題。

在實際的業務場景中出現記憶體溢位的問題,排查起來一般是十分困難繁瑣的,本文將通過結合一個簡單的例項來闡述排查的具體思路和步驟。

準備:

注意:在實際場景中,一般都是部署在Linux伺服器中的專案報出記憶體溢位的問題;為了儘可能還原出實際場景,本文也是將提前編寫好的可以觸發記憶體溢位的程式碼並打包成可執行的Jar包,然後放到伺服器中執行的。

1、準備可導致記憶體溢位的程式碼:

// 建立一個Java類
public class OutOfMemory {
    
    private String test;
    
    public OutOfMemory(String test){
        this.test = test;
    }
    
}

// 模擬記憶體溢位的發生
public class TestOOM {
    
    public static void main(String[] args) {
        List<OutOfMemory> list = new ArrayList<OutOfMemory>();
        
        while(true){
            /**
             * 無限建立OutOfMemory物件,直至將堆空間佔滿,並且建立的OutOfMemory物件一直被list集合物件引用著,
             * 導致GC也無法回收,最終出現堆記憶體溢位問題
             */
            list.add(new OutOfMemory("5656"));
            System.out.println("5656");
        }
    }
}
程式碼編寫完成後,使用開發工具匯出可執行的Jar包(TestOOM.jar)

2、準備Linux伺服器

可以直接使用centos或者Red Hat等都可以;

實戰:

1、將可執行的Jar包放到伺服器中執行:

①、可使用xshell、xftp工具將可執行的Jar包(Jar包叫:TestOOM.jar)放入到伺服器中;
②、使用命令執行Jar包;命令:
     java -Xms40m -Xmx70m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/tmp   -jar TestOOM.jar

注意:為了儘快模擬發生堆記憶體溢位,所以在啟動Jar包時,設定了一些引數;引數解析:
     1)、 -Xms40m 初始堆大小設定為40m
     2)、 -Xmx70m 最大堆大小設定為70m
     3)、 -XX:+HeapDumpOnOutOfMemoryError 出現堆記憶體溢位時,自動匯出堆記憶體 dump 快照
     4)、 -XX:HeapDumpPath=/usr/tmp 設定匯出的堆記憶體快照的存放地址為 /usr/tmp

2、執行成功後,使用JVM監控命令監控JVM的資訊:

①、jps命令:此命令是用來查詢與Java相關的程式的,並輸出程式號;下圖就是展示上面執行的Jar包的程式號:

②、jmap命令: jmap -heap 3324  此命令是查詢出程式號為 3324 的JVM中堆記憶體資訊;如下圖:

在圖中可以發現堆記憶體中新生代、年老代中 free 可用空間越來越小,這預示著即將會發生GC垃圾回收,從而使堆騰出更多的空間存放新建立的物件。

③、jstat命令:使用其監控JVM的效能資訊;例如:在本次排查記憶體溢位的問題中,會使用 jstat 命令監控 JVM的 GC垃圾回收的情況;
  命令: jstat -gcutil 3324 1000   意思是每1000毫秒查詢一次程式號為3324 的JVM的GC垃圾回收的情況;如下圖:

(1)、 YGC(堆中新生代GC)、FGC( FULL GC)為什麼觸發頻率這麼快呢?
答:由於堆記憶體空間不夠用了,需要通過GC垃圾回收將一些空間進行回收,用於存放新建立的物件。
( 2)、當堆記憶體空間不夠用時,GC具體會發生什麼呢?
答:
1)、當堆中的新生代空間不夠用時,會觸發YGC,對堆中新生代空間進行垃圾回收,同時垃圾回收後剩餘存活的物件會移動到堆中
老年代儲存,所以每次YGC後,堆中年老代中儲存的物件數量會增大;

2)、當堆的新生代即將發生YGC時,如果發現新生代中存活下來的物件比堆中年老代中剩餘的可用空間大的話,就會直接不進行YGC,
而會直接觸發FGC,FULL GC會對整個堆空間(新生代、老年代)以及方法區/永久代進行垃圾回收;

擴充套件:堆的結構圖:

3、出現記憶體溢位後,會自動生成快照,然後分析堆記憶體快照:

①、使用XFTP等工具將伺服器中的快照檔案匯出,本文堆記憶體快照檔案是以 hprof 為字尾的檔案;匯出快照檔案後,可以通過JDK自帶的 jvisualvm.exe 分析工具開啟進行分析。

jvisualvm.exe 是在哪裡呢?(以 windows 系統為例)
它是在 JDK的安裝目錄中的bin目錄下的

如圖:

②、使用 jvisualvm.exe 匯入快照檔案,如圖:
(1)、

(2)、

(3)、

通過分析堆記憶體快照得到的結論:
通過第(3)張圖,可以發現堆記憶體中有一個例項物件的佔比為 99.9%,可以確定是由於這個例項物件大量建立導致堆記憶體的溢位;
說到這,可以回過頭去看下我們自己編寫的可以觸發堆記憶體溢位的小程式,發現正是由於在 while(true) 死迴圈 中無線建立 OutOfMemory物件,導致堆記憶體空間被耗盡。


結語:
通過上面的實戰小例子,我們可以大體瞭解到在出現堆記憶體溢位時的排查步驟,但是在實際的場景中,這種情況可能會更加的複雜多變;

比如說,上面的那個小例子在出現的堆記憶體溢位時自動生成的堆記憶體快照檔案大小就達到了100多m,如果在實際的場景中,這個可能是非常巨大的,這時可能就會發生快照分析工具無法匯入堆記憶體快照。所以說,我們需要在平時通過不斷的學習,才能在未來出現問題時,能儘快定位問題並解決問題;程式設計師不光是能編寫好程式碼,還需要有解決問題的能力。

❤不要忘記留下你學習的足跡 [點贊 + 收藏 + 評論]嘿嘿ヾ

一切看文章不點贊都是“耍流氓”,嘿嘿ヾ(◍°∇°◍)ノ゙!開個玩笑,動一動你的小手,點贊就完事了,你每個人出一份力量(點贊 + 評論)就會讓更多的學習者加入進來!非常感謝! ̄ω ̄=

相關文章