Full GC 排查

发布于:2025-03-01 ⋅ 阅读:(104) ⋅ 点赞:(0)

在 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    GCT
  0.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 MATVisualVM 分析内存使用情况,检查是否有内存泄漏或对象无法回收的问题。

此外,可以使用:

jmap -histo <pid>

查看当前堆中的对象分布,重点关注大对象或数量异常的对象。

4. 使用 jconsoleVisualVM 监控

  • 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 的步骤

  1. 启用 GC 日志-Xlog:gc*-XX:+PrintGCDetails)分析 Full GC 触发原因。
  2. 使用 jstat -gcutil <pid> 1000 观察老年代是否满了。
  3. 使用 jmap -histo <pid>jmap -dump 分析堆对象,检查是否有内存泄漏或大对象。
  4. 使用 jconsoleVisualVM 监控 GC 活动,观察 Full GC 频率。
  5. 检查是否有 System.gc() 调用,可以通过 -XX:+DisableExplicitGC 禁用。
  6. 调整 JVM 参数,如 -Xmx, -XX:NewRatio, -XX:MetaspaceSize,或尝试 G1/ZGC。

这样可以快速找出 Full GC 发生的原因,并进行针对性的优化 🚀。


网站公告

今日签到

点亮在社区的每一天
去签到