在 Java 中,Full GC
(完全垃圾回收)會對整個堆(包括年輕代和老年代,甚至可能包括永久代/元空間)進行垃圾回收,通常會導致較長的停頓(STW,Stop-The-World)。如果 Full GC
頻繁發生,可能會影響應用的性能,甚至導致 OOM(OutOfMemoryError)。以下是排查 Full GC
的方法和工具:
1. 啟用 GC 日志進行分析
(1)JDK 8 及以下版本
在 JVM 啟動參數中添加:
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log
解釋:
-XX:+PrintGCDetails
:輸出 GC 詳細信息。-XX:+PrintGCDateStamps
:打印 GC 發生的時間戳。-Xloggc:gc.log
:指定 GC 日志文件。
GC 日志示例:
2025-02-28T10:00:00.123+0000: [Full GC (System.gc()) [PSYoungGen: 1024K->0K(2048K)] [ParOldGen: 4096K->1024K(8192K)], 0.2356780 secs]
分析重點:
Full GC (System.gc())
:說明 GC 是由System.gc()
觸發的。ParOldGen: 4096K->1024K(8192K)
:老年代的使用變化。0.2356780 secs
:GC 停頓時間。
(2)JDK 9 及以上
JDK 9 引入了 統一日志框架(JEP 158),可以使用 -Xlog
進行更細粒度的 GC 日志控制:
-Xlog:gc*:file=gc.log:time,uptime,level,tags
示例:
[2025-02-28T10:00:00.123+0000][info][gc] GC(5) Pause Full (G1 Evacuation Pause) 235ms
2. 使用 jstat
監控 GC
jstat
是 JDK 自帶的工具,可以用來實時監控 GC 的情況。
(1)查看 GC 統計信息
jstat -gcutil <pid> 1000
示例輸出:
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT0.00 98.65 64.32 92.12 80.45 65.23 150 12.34 20 30.12 42.46
重點關注:
O
(Old Gen):如果O
長期接近 100%,說明老年代快滿了,可能會觸發Full GC
。FGC
(Full GC 次數):如果這個值增長很快,說明Full GC
頻繁發生。FGCT
(Full GC 耗時):觀察Full GC
是否導致了長時間的 STW。
(2)查看具體 GC 詳細信息
jstat -gc <pid> 1000
可以看到 Eden、Survivor、Old 區域的內存變化,幫助判斷是否因老年代空間不足導致 Full GC
。
3. 使用 jmap
檢查堆內存
如果懷疑 Full GC
頻繁發生是由于內存不足,可以用 jmap
生成 堆轉儲文件(Heap Dump) 進行分析:
jmap -dump:format=b,file=heapdump.hprof <pid>
然后用 Eclipse MAT 或 VisualVM 分析內存使用情況,檢查是否有內存泄漏或對象無法回收的問題。
此外,可以使用:
jmap -histo <pid>
查看當前堆中的對象分布,重點關注大對象或數量異常的對象。
4. 使用 jconsole
或 VisualVM
監控
-
JConsole:
jconsole
- 連接到目標 JVM 后,查看 “內存”(Memory) 選項卡,觀察老年代的占用情況。
- 如果
Old Gen
長期接近 100%,可能會觸發Full GC
。
-
VisualVM:
jvisualvm
- 連接到目標進程,打開 “監視”(Monitor) 選項卡,觀察 GC 活動。
- 使用 “Profiler”(分析器) 記錄 GC 發生的時間和影響。
5. 檢查 Full GC
觸發原因
(1)老年代空間不足
- 觀察老年代(Old Gen)占用情況,如果頻繁接近 100%,說明
Full GC
可能是由于老年代空間不足導致的。 - 優化方案:
- 增大老年代:
-Xmx4g -Xms4g
- 調整
GC
算法(如 G1 或 ZGC):-XX:+UseG1GC
- 調整老年代比例:
-XX:NewRatio=2
- 增大老年代:
(2)過多的 System.gc()
- 某些代碼可能顯式調用了
System.gc()
,強制觸發Full GC
。 - 檢查代碼是否有
System.gc()
調用,可以禁用:-XX:+DisableExplicitGC
(3)大對象直接進入老年代
- 如果對象過大,可能會直接分配到老年代,導致
Full GC
。 - 優化方案:
- 增大
-XX:PretenureSizeThreshold
以避免大對象直接進入老年代:-XX:PretenureSizeThreshold=16m
- 使用 G1GC,可以通過
-XX:InitiatingHeapOccupancyPercent
控制老年代回收時機。
- 增大
(4)元空間(Metaspace)不足
- 在 JDK 8+,類元數據存放在 元空間(Metaspace),如果元空間不足也會觸發
Full GC
。 - 優化方案:
- 增大元空間:
-XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=1g
- 增大元空間:
6. 結論
快速排查 Full GC
的步驟
- 啟用 GC 日志(
-Xlog:gc*
或-XX:+PrintGCDetails
)分析Full GC
觸發原因。 - 使用
jstat -gcutil <pid> 1000
觀察老年代是否滿了。 - 使用
jmap -histo <pid>
或jmap -dump
分析堆對象,檢查是否有內存泄漏或大對象。 - 使用
jconsole
或VisualVM
監控 GC 活動,觀察Full GC
頻率。 - 檢查是否有
System.gc()
調用,可以通過-XX:+DisableExplicitGC
禁用。 - 調整 JVM 參數,如
-Xmx
,-XX:NewRatio
,-XX:MetaspaceSize
,或嘗試 G1/ZGC。
這樣可以快速找出 Full GC
發生的原因,并進行針對性的優化 🚀。