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