1. 清楚從程序角度,有哪些原因導致FGC?
-
大對象:系統一次性加載了過多數據到內存中(比如SQL查詢未做分頁),導致大對象進入了老年代。
-
內存泄漏:頻繁創建了大量對象,但是無法被回收(比如IO對象使用完後未調用close方法釋放資源),先引發FGC,最後導致OOM.
-
程序頻繁生成一些長生命週期的對象,當這些對象的存活年齡超過分代年齡時便會進入老年代,最後引發FGC. (即本文中的案例)
-
程序BUG導致動態生成了很多新類,使得 Metaspace 不斷被佔用,先引發FGC,最後導致OOM.
-
代碼中顯式調用了gc方法,包括自己的代碼甚至框架中的代碼。
-
JVM參數設置問題:包括總內存大小、新生代和老年代的大小、Eden區和S區的大小、元空間大小、垃圾回收算法等等。
2. 清楚排查問題時能使用哪些工具
-
公司的監控系統:大部分公司都會有,可全方位監控JVM的各項指標。
-
JDK的自帶工具,包括jmap、jstat等常用命令:
# 查看堆內存各區域的使用率以及GC情況
jstat -gcutil -h20 pid 1000
# 查看堆內存中的存活對象,並按空間排序
jmap -histo pid | head -n20
# dump堆內存文件
jmap -dump:format=b,file=heap pid (會停掉程序,謹慎使用)
-
可視化的堆內存分析工具:JVisualVM、MAT等
3. 排查指南
-
查看監控,以瞭解出現問題的時間點以及當前FGC的頻率(可對比正常情況看頻率是否正常)
-
瞭解該時間點之前有沒有程序上線、基礎組件升級等情況。
-
瞭解JVM的參數設置,包括:堆空間各個區域的大小設置,新生代和老年代分別採用了哪些垃圾收集器,然後分析JVM參數設置是否合理。
-
再對步驟1中列出的可能原因做排除法,其中元空間被打滿、內存泄漏、代碼顯式調用gc方法比較容易排查。
-
針對大對象或者長生命週期對象導致的FGC,可通過 jmap -histo 命令並結合dump堆內存文件作進一步分析,需要先定位到可疑對象。
-
通過可疑對象定位到具體代碼再次分析,這時候要結合GC原理和JVM參數設置,弄清楚可疑對象是否滿足了進入到老年代的條件才能下結論