記一次內存溢出排查過程

有一個服務經常會停止服務,一開始沒特別注意,出問題就重新部署

後來有一次重啓前看了眼 GC,發現一直在 Full GC:

[149644.445s][info][gc,start       ] GC(2210) Pause Full (Allocation Failure)
[149644.447s][info][gc,phases,start] GC(2210) Phase 1: Mark live objects
[149644.708s][info][gc,phases      ] GC(2210) Phase 1: Mark live objects 261.113ms
[149644.708s][info][gc,phases,start] GC(2210) Phase 2: Compute new object addresses
[149644.771s][info][gc,phases      ] GC(2210) Phase 2: Compute new object addresses 62.603ms
[149644.771s][info][gc,phases,start] GC(2210) Phase 3: Adjust pointers
[149644.903s][info][gc,phases      ] GC(2210) Phase 3: Adjust pointers 131.929ms
[149644.903s][info][gc,phases,start] GC(2210) Phase 4: Move objects
[149644.903s][info][gc,phases      ] GC(2210) Phase 4: Move objects 0.011ms
[149644.905s][info][gc,heap        ] GC(2210) DefNew: 314553K->314553K(314560K)
[149644.905s][info][gc,heap        ] GC(2210) Tenured: 699071K->699071K(699072K)
[149644.905s][info][gc,metaspace   ] GC(2210) Metaspace: 83387K->83387K(1126400K)
[149644.905s][info][gc             ] GC(2210) Pause Full (Allocation Failure) 989M->989M(989M) 459.957ms
[149644.905s][info][gc,cpu         ] GC(2210) User=0.46s Sys=0.00s Real=0.46s
[149644.907s][info][gc,start       ] GC(2211) Pause Full (Allocation Failure)
[149644.910s][info][gc,phases,start] GC(2211) Phase 1: Mark live objects
[149645.168s][info][gc,phases      ] GC(2211) Phase 1: Mark live objects 258.422ms
[149645.168s][info][gc,phases,start] GC(2211) Phase 2: Compute new object addresses
[149645.231s][info][gc,phases      ] GC(2211) Phase 2: Compute new object addresses 63.401ms
[149645.231s][info][gc,phases,start] GC(2211) Phase 3: Adjust pointers
[149645.365s][info][gc,phases      ] GC(2211) Phase 3: Adjust pointers 133.363ms
[149645.365s][info][gc,phases,start] GC(2211) Phase 4: Move objects
[149645.367s][info][gc,phases      ] GC(2211) Phase 4: Move objects 1.892ms
[149645.368s][info][gc,heap        ] GC(2211) DefNew: 314559K->312083K(314560K)
[149645.368s][info][gc,heap        ] GC(2211) Tenured: 699071K->699071K(699072K)
[149645.368s][info][gc,metaspace   ] GC(2211) Metaspace: 83387K->83387K(1126400K)
[149645.368s][info][gc             ] GC(2211) Pause Full (Allocation Failure) 989M->987M(989M) 460.962ms
[149645.368s][info][gc,cpu         ] GC(2211) User=0.46s Sys=0.00s Real=0.46s

服務限制了 4G 內存,在沒有指定 -Xmx 的情況下只能使用 1G 內存,

瞭解更多 GC 內容看這裏: HotSpot Virtual Machine Garbage Collection Tuning GuideGC Tuning: In Practice

當時着急用服務就又直接重啓了,直到今天,纔在重啓前儘可能保留了更多的信息。

下面是監控和 GC 的實時截圖:
在這裏插入圖片描述
服務啓動兩天後,實際上在1天前就已經佔滿內存開始頻繁 GC。

GC 實時信息:
在這裏插入圖片描述
FGC 比 YGC 還多,E年輕代滿了,O老年代也滿的。每次可用的只有 S1, S2 中的不到 10% 的空間。

這次本來要導出 dump 信息,可惜手快重啓沒拷出來。等新服務啓動一段時間後,在線上通過 jps 查看 id,然後使用 jmap -dump:live,format=b,file=heap.bin <pid> 導出 dump 信息。

jmap 詳細資料: https://docs.oracle.com/en/java/javase/11/tools/jmap.html#GUID-D2340719-82BA-4077-B0F3-2803269B7F41

導出 heap.bin 後,先通過 VisualVM 打開了,能看到有限的信息,關鍵還不熟悉 OQL 語法,很不方便,因此使用 內存分析器(MAT) 進行分析。

Memory Analyzer (MAT) 內存分析器(MAT)
Eclipse Memory Analyzer 是一個快速且功能豐富的 Java 堆分析器,它可以幫助您發現內存泄漏並減少內存消耗。
使用內存分析器分析數億個對象的高效堆轉儲,快速計算對象的保留大小,查看是誰阻止垃圾收集器收集對象,運行報告自動提取泄漏嫌疑人。
在這裏插入圖片描述

用 mat 打開後,根據提示信息找到了下面的地方:
在這裏插入圖片描述

XDocReportRegistry 中的 MapCacheStoreage 佔用了 63.81% 的空間,從名字來看是緩存,而緩存是最容易出現內存溢出的地方。
在這裏插入圖片描述
在工具類中有如下調用:
在這裏插入圖片描述

這裏的 loadReport 如下:
在這裏插入圖片描述

這個方法默認是緩存的,但是使用的地方每次都是傳入新的 InputStream,緩存的 IXDocReport 根本沒有用上,因此該方法只要執行一次都會導致緩存增加一個,最終會將內存佔滿。

最簡單的解決方法就是調用 loadReport 的時候指定 cacheReport=false 禁用緩存。

解決了這裏內存溢出的問題後,服務所用內存應該會大幅度降低,等服務運行一兩天後再補充兩張資源佔用圖。

2019 年博客總結 中提過:

因爲我發現很多技術博客的內容中,很少涉及相關內容的出處,如果我只瞭解了別人博客提到的,關注的內容,看不到全貌,我自己總感覺不夠,我想看到更詳盡的資料,即使用到的只是其中一部分,但是提供出處就可以讓人有了解更多的機會,對你博客內容的可信度提高很多。因此 2020 年會多一些輪子似的博客,但是會提供詳細的出處和擴展資料。

所以,本文中提供的所有鏈接的價值要遠遠大於本文。如果有興趣,一定要看!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章