如何判断 Java 应用是否发生 GC 问题?

76 阅读4分钟

GC判断

如何判断 Java 应用是否发生 GC 问题?通过以下命令、指标和现象快速诊断:


一、常用命令及关键指标

1. jstat 实时监控
jstat -gc <pid> [间隔时间] [次数]
# 示例:每 1 秒输出一次 GC 统计信息
jstat -gc 12345 1000

关键字段解释

指标含义异常表现
YGC年轻代 GC 次数短时间大幅增加 → 年轻代过小/对象分配过快
YGCT年轻代 GC 总耗时单次耗时 > 100ms → GC效率低/存活对象多
FGCFull GC 次数频繁增加(如每分钟多次)→ 内存泄漏/老年代满
FGCTFull GC 总耗时占比超过 5% → 应用吞吐量下降
OU老年代当前使用量(KB)OU 持续接近 OC(总容量) → 可能内存泄漏
MU元空间(Metaspace)使用量MU 接近 MC → 元空间不足或类加载泄露

2. jmap 内存分析
# 生成堆转储文件(用于内存泄漏分析)
jmap -dump:format=b,file=heapdump.hprof <pid>

# 查看堆内存摘要(快速判断内存分布)
jmap -heap <pid>
3. GC 日志分析

开启日志后,重点关注以下内容:

  • Full GC 触发原因(如 Allocation FailureMetadata GC Threshold)。
  • GC 停顿时间(标记为 Pause 的毫秒值)。
  • 内存回收前后的占比(如 Eden: 100M->0M 表示 Eden 区完全清空)。

二、常见 GC 问题及诊断步骤

1. 频繁 Full GC
  • 现象FGC 次数骤增,FGCT 耗时高,应用卡顿。
  • 检查指标
    • OU 持续接近 OC(老年代容量)。
    • jmap -heap 显示老年代占用率 > 90%。
  • 诊断步骤
    1. jmap -dump 生成堆转储文件。
    2. 使用 Eclipse MAT 分析大对象或无法回收的类(如 java.lang.Object[ ] 占比较大可能是集合类未释放)。
  • 可能原因
    • 内存泄漏(对象未释放)。
    • 大对象直接进入老年代(未配置 -XX:PretenureSizeThreshold)。

2. Young GC 频繁且耗时高
  • 现象YGC 每分钟数十次,YGCT 单次超过 200ms。
  • 检查指标
    • Eden 区容量(EC)较小(如默认值 82MB 不适合高分配场景)。
    • 每次 Young GC 后存活对象过多(Survivor 区使用量 S0U/S1U 溢出)。
  • 解决步骤
    • 增大年轻代(如 -Xmn2G)。
    • 调整 Survivor 区比例(例如 -XX:SurvivorRatio=6 让 Eden:S0:S1 = 6:1:1)。

3. 元空间(Metaspace)OOM
  • 现象MU 接近 MC,触发 Metadata GC Threshold 的 Full GC。
  • 检查命令
    # 跟踪类加载/卸载
    -XX:+TraceClassLoading -XX:+TraceClassUnloading
    
  • 常见原因
    • 动态生成类过多(如反射、动态代理、Groovy 脚本引擎)。
    • 未设置元空间上限(默认无限增长)。
  • 解决方案
    • 限制元空间大小-XX:MaxMetaspaceSize=256M
    • 排查类加载泄露(如重复加载相同类)。

4. 内存碎片化(CMS/G1 常见)
  • 现象:Full GC 日志提示 Promotion Failed(晋升失败)/ Concurrent Mode Failure
  • 诊断步骤
    • 查看 GC 原因:日志中是否因碎片化触发 Full GC。
    • G1 分区情况(使用 jstat -gccapacity <pid> 检查老年代分区碎片)。
  • 解决方案
    • 增加堆大小,或 减少 -XX:InitiatingHeapOccupancyPercent(降低混合 GC 触发阈值)
    • 对于 CMS,启用压缩 Full GC:-XX:+UseCMSCompactAtFullCollection

三、工具推荐

1. 可视化分析
工具用途示例场景
GCViewer分析 GC 日志,生成吞吐量报表定位 Young/Full GC 耗时分布
GCEasy在线上传 GC 日志,自动分析结论快速判断 STW 时间是否超阈值
Eclipse MAT分析堆转储文件查找内存泄漏占位对象
2. 高级分析
  • JFR(Java Flight Recorder):实时监控 GC 事件和内存分配。
    # 启用 JFR 记录
    -XX:StartFlightRecording=duration=60s,filename=recording.jfr
    
  • Async-Profiler:生成内存分配火焰图。
    ./profiler.sh -e alloc -d 60 -f alloc.svg <pid>
    

四、快速参考表

问题类型关键指标异常值示例修复建议
频繁 Full GCFGC > 5次/分钟, OU ≈ OCFGC=200次/小时, OU=1.9G/2G检查内存泄漏,增大老年代(-Xmx
Young GC 耗时高YGCT/YGC > 100ms, YGC > 50次/分钟YGCT=500ms, YGC=100次/分增大 Eden 区(-Xmn),减少存活对象
元空间 OOMMU ≈ MC (>95%)MU=250M/256M限制 MaxMetaspaceSize,检查类加载泄露
内存碎片化Full GC 触发 Promotion Failed日志中频繁碎片化警告切换到 G1 回收器,启用内存压缩

五、实战示例

1. 发现应用卡顿,初步怀疑 GC
# 快速查看 FGC 增长情况:
$ jstat -gc 12345 1000 5

输出示例:
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    YGC     YGCT    FGC    FGCT     GCT 
1024.0 1024.0  0.0    0.0    8192.0   8192.0    20480.0    20478.3   4864.0 4800.6   15    0.250    3      0.900    1.150
...(观察 OU 接近 OC,FGCT 快速增长)

结论:老年代即将填满(OU=20478.3/20480.0),频繁 Full GC(3次)导致停顿。

2. 生成 Heap Dump 分析
jmap -dump:live,format=b,file=leak.hprof 12345

用 Eclipase MAT 打开 leak.hprof,发现 com.example.Cache 类持有 1.8GB 的 HashMap 未释放 → 修复代码释放缓存。


通过以上指标与工具的组合使用,能够快速定位 GC 问题的根本原因,并有针对性地优化 JVM 配置或代码逻辑。