模擬實戰排查堆內存溢出(java.lang.OutOfMemoryError: Java heap space)問題

前言:

模擬實戰中排查堆內存溢出(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,如果在實際的場景中,這個可能是非常巨大的,這時可能就會發生快照分析工具無法導入堆內存快照。所以說,我們需要在平時通過不斷的學習,才能在未來出現問題時,能儘快定位問題並解決問題;程序員不光是能編寫好代碼,還需要有解決問題的能力。


謝謝大家閱讀,鑑於本人水平有限,如有問題敬請提出。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章