在 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
发生的原因,并进行针对性的优化 🚀。