GC判断
如何判断 Java 应用是否发生 GC 问题?通过以下命令、指标和现象快速诊断:
一、常用命令及关键指标
1. jstat 实时监控
jstat -gc <pid> [间隔时间] [次数]
# 示例:每 1 秒输出一次 GC 统计信息
jstat -gc 12345 1000
关键字段解释:
| 指标 | 含义 | 异常表现 |
|---|---|---|
| YGC | 年轻代 GC 次数 | 短时间大幅增加 → 年轻代过小/对象分配过快 |
| YGCT | 年轻代 GC 总耗时 | 单次耗时 > 100ms → GC效率低/存活对象多 |
| FGC | Full GC 次数 | 频繁增加(如每分钟多次)→ 内存泄漏/老年代满 |
| FGCT | Full 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 Failure、Metadata GC Threshold)。 - GC 停顿时间(标记为
Pause的毫秒值)。 - 内存回收前后的占比(如
Eden: 100M->0M表示 Eden 区完全清空)。
二、常见 GC 问题及诊断步骤
1. 频繁 Full GC
- 现象:
FGC次数骤增,FGCT耗时高,应用卡顿。 - 检查指标:
OU持续接近OC(老年代容量)。jmap -heap显示老年代占用率 > 90%。
- 诊断步骤:
- 用
jmap -dump生成堆转储文件。 - 使用 Eclipse MAT 分析大对象或无法回收的类(如
java.lang.Object[ ]占比较大可能是集合类未释放)。
- 用
- 可能原因:
- 内存泄漏(对象未释放)。
- 大对象直接进入老年代(未配置
-XX:PretenureSizeThreshold)。
2. Young GC 频繁且耗时高
- 现象:
YGC每分钟数十次,YGCT单次超过 200ms。 - 检查指标:
- Eden 区容量(
EC)较小(如默认值 82MB 不适合高分配场景)。 - 每次 Young GC 后存活对象过多(
Survivor区使用量S0U/S1U溢出)。
- Eden 区容量(
- 解决步骤:
- 增大年轻代(如
-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 GC | FGC > 5次/分钟, OU ≈ OC | FGC=200次/小时, OU=1.9G/2G | 检查内存泄漏,增大老年代(-Xmx) |
| Young GC 耗时高 | YGCT/YGC > 100ms, YGC > 50次/分钟 | YGCT=500ms, YGC=100次/分 | 增大 Eden 区(-Xmn),减少存活对象 |
| 元空间 OOM | MU ≈ 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 配置或代码逻辑。