這兩天公司的一個程序出現問題,頻繁出現內存溢出錯誤OutOfMemory:GC overhead limit exceeded.
雖然知道這個錯誤的原因是因爲Java虛擬機在頻繁進行垃圾回收,使用了98%的時間進行垃圾回收,但是實際回收了不到2%的內存。但結合到代碼中,還是無法知道爲什麼會出現這個問題。
程序的內存設置爲3G,6G都不行,快的話10分鐘就內存溢出。沒有辦法,只能給Java程序加上命令行
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:\mat.bin
加上之後,如果程序再發生內存溢出,就會在指定位置生成內存映像文件(PS:如果等內存溢出之後,再去手動用jmap生成內存映像文件,可能那時的內存已經釋放完了,導出來只有一點點)。
等了幾天,程序照常內存溢出,成功生成了內存映像文件。
下載映像文件,使用memory Analyzer軟件進行分析。
首先下載memory Analyzer,下載官網是https://www.eclipse.org/downloads/download.php?file=/mat/1.10.0/rcp/MemoryAnalyzer-1.10.0.20200225-win32.win32.x86_64.zip
下載頁面可以選服務器,記得選國內的服務器。
下載後解壓,記得修改MemoryAnalyzer.ini,把最大內存改大一點,不然都不夠mat.bin內存映像文件用。
打開MemoryAnalyzer.exe,打開內存映像文件
打開後,可以看到,整個程序的大概內存情況,大概用了2.8G,鼠標放上去,顯示其中單個類關聯的大小就佔了2.7個G
單擊餅圖,出現菜單,點擊List objects -> with outging references
意思是查看這個類裏面包含的對象
在這個頁面中,點擊第三列Retained Heap,讓它從大到小排序
可以看到,其中的<Java Local> java.util.ArrayList@0x70c90de90這個變量佔用了2.8個G左右
<Java Local>表示是局部變量,也就是線程棧幀中的變量,而不是類的成員變量。
也就是說明,這個ArrayList是在某個方法中生成的,這個方法是關聯到AutoReForwardThread線程。
點開ArrayList,可以看到每個元素的類型是什麼,還有總共有4萬多個元素,元素的大小有96k
查看這些元素裏面的值,大概知道對應的業務模塊是什麼,然後找到代碼,看看是否有一個局部變量List,保存了這些對象
最後找到這裏,這裏從數據庫中查詢出數據,放到list中
最後驗證了數據庫,確實查出來幾十萬條數據,從而導致了Java內存溢出。
總結:List list局部變量在ClassA.method1()中定義,該方法在ThreadB中進行調用,那麼list能關聯到ThreadB,而關聯不到ClassA。所以需要深刻理解Java中的成員變量和局部變量的存放位置,才能明白內存印象文件中類、變量、局部變量、線程中的關聯關係。