gc日志是jvm内存行为的实时录像,核心在于暂停时间、内存趋势、对象生命周期及与业务指标的关联;典型日志含时间戳、gc类型与原因、各代内存变化、stw耗时、附加标签五部分。

GC 日志不是“发生了多少次回收”的计数器,而是 JVM 内存行为的实时录像。真正影响服务稳定性的,是每次回收带来的暂停时间、内存变化趋势、对象生命周期特征,以及这些数据和业务指标之间的对应关系。
看清楚日志里到底在说什么
一条典型 GC 日志包含五个关键信息块:时间戳(绝对时间或 uptime)、GC 类型与原因、各代内存变化(如 PSYoungGen: 1024K->512K(1536K))、STW 耗时、附加标签(如 gc,heap,time,cause)。其中:
-
内存变化格式统一为“区域名: 回收前→回收后(总容量)”,比如
Eden: 960M->0M(960M)表示 Eden 区被清空;ParOldGen: 3200M->2800M(4096M)表示老年代释放了 400MB -
触发原因比 GC 类型更重要:
Allocation Failure是正常年轻代回收;Metadata GC Threshold提示元空间快满;G1 Evacuation Pause属于 G1 的常规操作;而System.gc()则是代码主动干预,需排查 -
耗时字段要区分 STW 和并发阶段:带
Pause前缀的是应用停顿时间(如Pause Young: 28.4ms),必须关注;CMS-concurrent-mark这类不暂停应用,仅反映后台工作开销
四个核心指标不能只看单点数值
高频、长停、满堆、突增——这四类信号需结合趋势判断,孤立数值容易误判:
- YGC 频率与耗时:每分钟超 30 次且平均 >20ms,说明 Eden 太小或对象创建过猛;若耗时陡增但频率不变,可能是 Survivor 区溢出导致提前晋升
-
FGC 次数:线上环境只要出现就属风险;一天超过 3 次必须介入;但单次 FGC 若仅因
Metadata GC Threshold引发,调大-XX:MaxMetaspaceSize即可,不必恐慌 -
老年代增长速度:用日志中
ParOldGen或G1OldGen的“回收后占用”连续采样,画折线图;匀速爬升属正常缓存积累;若某次 YGC 后老年代猛增 500MB,大概率是批量任务中大量对象寿命超出 Survivor 阈值 - STW 总占比:累计暂停时间 ÷ 总运行时间 >2%,系统已处于亚健康状态;单次 >500ms,P99 接口必然超时
把 GC 日志放进业务上下文里读
脱离请求链路、线程池、CPU 使用率看 GC 日志,就像只听心跳不量血压。真正有效的分析必须对齐时间窗口:
- 将 GC 暂停时间点(精确到毫秒)与接口 P99 延迟尖峰重叠,才能确认是否 GC 导致抖动;若延迟升高时 GC 平稳,问题大概率在下游依赖或锁竞争
- 观察 GC 高峰是否同步伴随线程池队列堆积、CPU usage 突升;三者共振,说明应用正承受突发流量压力,而非单纯 JVM 配置问题
- 不同 GC 器关注重点不同:G1 要盯
Mixed GC频次和humongous allocation记录;ZGC/Shenandoah 则重点看Concurrent cycle是否被频繁打断,而非 Full GC 次数 - 分配速率(Allocation Rate)比 GC 次数更能暴露问题根源;配合
jcmd <pid> VM.native_memory summary</pid>或 JFR 采样,可定位到具体类(如StringBuilder、byte[]、JSON 序列化中间对象)在疯狂创建
工具只是放大镜,不是诊断书
GCViewer 和 GCEasy 能快速绘出停顿分布图、内存趋势线,但它们不会告诉你“为什么 SurvivorRatio=8 导致晋升异常”。工具的价值在于:
- 用 GCViewer 查看“Pause Young”事件的时间分布直方图,识别是否存在长尾停顿(如 95% 在 10ms 内,但 5% 超过 100ms),提示存在大对象或碎片化
- 用 GCEasy 的“Memory Leak Suspect”模块标记持续上涨的老年代占用,再人工回溯对应时间段的日志,找触发该上涨的首次 YGC 晋升量
- 导出 GC 日志的 CSV 数据,用 Excel 或 Grafana 关联业务监控指标,建立“GC 暂停 × P99 × 队列长度”三维视图,避免归因偏差











