GC 日志与 jstat:先看清发生了什么,再谈调优
更新时间:2026-09-20。GC 调优的第一原则是先测量、后调整,绝不拍脑袋加参数。
jstat看分代使用率与 GC 计数,GC 日志看每次回收的细节。堆分代概念见 JVM 堆分代,收集器差异见 G1 与 G1 vs CMS。
一、jstat 两个最常用视图
bash
jstat -gcutil <pid> 1000 10 # 每 1 秒一次共 10 次,看百分比
jstat -gc <pid> 1000 # 看各代容量与使用量(KB)-gcutil 输出列:
| 列 | 含义 |
|---|---|
| S0/S1 | 两块 Survivor 使用率(%) |
| E | Eden 使用率 |
| O | 老年代使用率 |
| M | Metaspace 使用率 |
| YGC / YGCT | Young GC 次数 / 累计耗时(秒) |
| FGC / FGCT | Full GC 次数 / 累计耗时 |
| GCT | GC 总耗时 |
判读方法:连续采样观察 Eden 涨到 100% → YGC 后回落到低位、对象在 S0/S1 间倒、O 缓慢爬升 是健康节奏;重点算 YGCT/YGC 单次 Young GC 停顿与频率,以及 O 区在 Full GC 后能否真正下降。
二、打开 GC 日志
bash
# JDK 9+(统一日志 -Xlog)
-Xlog:gc*,gc+age=trace,safepoint:file=/data/gc/app-gc.log:time,uptime,level,tags:filecount=10,filesize=50M
# JDK 8
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/data/gc/app-gc.log -XX:+UseGCLogFileRotation ...一条 Young GC 日志的关键信息:回收前→后新生代容量变化、堆整体变化、暂停时长(Pause Young)、age trace 能看到对象年龄分布(判断晋升年龄)。Full GC 会标出原因(Allocation Failure / Metadata GC Threshold / System.gc() / Humongous Allocation)。

三、常见症状与成因
| 症状 | 可能原因 | 先做什么 |
|---|---|---|
| Young GC 很频繁但每次很快 | Eden 太小、对象分配猛 | 看分配速率,适当调大新生代 |
| 老年代持续涨、Full GC 后不降 | 内存泄漏 / 缓存无限增长 | jmap 直方图 + 堆转储分析 |
| 晋升年龄很小就进老年代 | Survivor 太小、动态年龄判断 | 看 gc+age,调 SurvivorRatio |
| 周期性 Full GC、Metaspace 涨 | 类加载泄漏(动态代理/热部署) | 查 ClassLoader,限 MaxMetaspaceSize |
| 大对象直接入老年代 | 超大数组/字节缓冲 | 减小批大小,G1 调 Region/大对象阈值 |
| 无端 Full GC | 代码显式 System.gc() | -XX:+ExplicitGCInvokesConcurrent |
四、调优顺序(别本末倒置)
- 先修代码与内存泄漏,再谈参数——80% 的 GC 问题是不合理的对象保留,不是 JVM 没调好。
- 固定
-Xms = -Xmx,避免运行时扩缩容;先选定收集器(JDK 9+ 默认 G1,大堆低延迟评估 ZGC,见 ZGC/Shenandoah)。 - 让新生代大小、晋升速率与 RT 预算匹配;G1 主要设
-XX:MaxGCPauseMillis与堆大小,少手工钉死各代。 - 每次只改一个变量,对比 GC 频率、单次停顿、吞吐和业务 RT,而不是只看 GC「次数变少」。
坑
- 只盯 GC 次数不看停顿与业务 RT:次数少但每次 STW 很长同样致命,要看 P99。
- 一上来就套网上参数模板:堆大小、核心数、分配速率不同,模板可能更糟。
- 把老年代增长全当泄漏:正常缓存、大对象、Metaspace 都会涨,结合 Full GC 后是否回落下结论。
- 没开 GC 日志就想复盘:线上务必默认开启并轮转,事故时才有现场。
- 频繁 System.gc() 被忽视:NIO 直接内存回收也可能触发,日志里能看到来源。
总结
用 jstat -gcutil 连续观察 Eden/Survivor/Old/Metaspace 使用率与 YGC/FGC 计数和累计耗时,用 -Xlog:gc 抓每次回收的容量变化、暂停时长、对象年龄与触发原因;健康节奏是 Eden 周期性填满触发快速 Young GC、老年代缓慢增长且 Full GC 后能回落,Young GC 过频看新生代与分配速率,Full GC 频繁且老年代不降优先怀疑内存泄漏并上 jmap/堆转储;调优永远先修泄漏、固定 Xms=Xmx、选对收集器,一次只改一个变量并用业务 P99 验证。* 堆溢出的具体分类定位见 OOM 排查。