这是 GC 系列的 最实战、最重要 一篇。
我们从真实线上场景出发,讲 排查思路、判断逻辑、最终定位方法。
无论你的系统是:
RT 周期性抖动
CPU 飙升
FullGC 频繁
内存“慢性增长”
线上偶发停顿
本篇提供的流程足以覆盖 90% 的实际问题。
一、GC 事故排查的总流程(黄金 5 步)
把所有 GC 问题归纳到一个非常清晰的流程:
① RT 波动 or CPU 高?先确定表现
常见表现:
第一步永远先看症状,不要直接看 GC 日志。
② 确认是 GC 问题还是业务问题?
你要排除两个常见误伤:
大量 IO 堵塞 → 误以为是 GC
大量线程竞争 → 误以为是 GC
判断技巧:
jstat -gcutil <pid> 1000 10如果 在异常时间段 出现以下情况:
YGC/FULLGC 次数猛增
Old 区持续升高并不掉
Metaspace 持续上涨
GC 时间明显加长
才是 GC 方向。
③ 用 GC 日志确认“什么 GC 在拖累系统”
你需要关注 4 个关键点:
哪种 GC 占用时间最长?
YGC 是否过频?
MixedGC 是否不足?
Old 区是否被回收?
我们用 G1 为例解析。
④ 判断“GC 回收不掉的原因”是什么
通常只有 3 类原因:
原因 1:对象产生太快 → Young 挂不住→ GC 超频
表现:
YGC 明显增加
Region 全部耗尽
Evacuation time 拉长
原因 2:对象回收不掉 → 老年代涨 → MixedGC 不足
表现:
Old 区曲线单方向上升
MixedGC 次数很少
有 FullGC 记录
原因 3:存在内存泄漏 → Old 区只增不减
表现:
连续几轮 GC 后 Old 区仍不下降
精确符合泄漏行为
⑤ 使用 MAT / Arthas / GCViewer 定位根因
根据原因不同,用不同工具定位:
Young 产生太快 → 分析对象分配热点
MixedGC 不足 → 调整 IHOP
老年代对象不降 → MAT 分析泄漏路径
这就是 GC 排查的黄金路线图。
二、最常见的 GC 事故及其定位方法(企业真实案例)
这里给你 5 个生产事故模式,每一个都是线上常见大坑。
事故 1:周期性 RT 峰值(业务并发不高,但卡顿明显)
表现:
每隔几秒到几十秒,RT 出现周期性波峰
CPU 正常
GC 日志中 YoungGC 响应时间稳定,但有周期性 MixedGC
GC 日志特征:
GC pause (G1 Mixed) ... 70ms原因:MixedGC 暂停超过 SLA。
解决方案:
-XX:MaxGCPauseMillis=150
-XX:InitiatingHeapOccupancyPercent=30IHOP 降低能提前开始并发标记,让 MixedGC 分布更均匀。
事故 2:CPU 100%,但业务量正常
表现:
CPU 满
RT 降到几百毫秒甚至几秒
GC 日志显示 YoungGC 每秒几十次
根因:Young 区太小
解决方案:扩大 Young 区
-XX:G1NewSizePercent=25
-XX:G1MaxNewSizePercent=60如果对象分配非常快:
-XX:ParallelGCThreads=<CPU数>事故 3:FullGC 周期性爆发(服务基本不可用)
表现:
大量 FullGC(0.5s~3s)
Old 区持续涨
YoungGC + MixedGC 都回收不动
几乎一定是:
MixedGC 触发太晚,老年代积压太多不可回收对象。
解决方案:提前开始并发标记
-XX:InitiatingHeapOccupancyPercent=20如果堆 ≥ 8G,这个调整非常关键。
事故 4:老年代持续上升但 YoungGC 正常 → 内存泄漏
典型表现:
Old 区只增不减
YoungGC 和 MixedGC 均有效
RT 稳定直到最终 OOM
定位泄漏工具:
方案 1:Arthas
heapdump方案 2:MAT 重点看 Dominator Tree
关注:
单实例占用巨大
集合类对象(HashMap、ArrayList)不断膨胀
缓存未过期(LocalCache)
自定义 ThreadLocal 没有 remove()
方案 3:jmap + grep 生存代分析
jmap -histo:live <pid>排查哪些类增长明显。
事故 5:Metaspace 不断上涨导致 OOM
常见由:
频繁动态生成类(cglib、proxy)
大量 SPI、ServiceLoader
热加载类未卸载
解决:
-XX:MaxMetaspaceSize=512m
-XX:MinMetaspaceFreeRatio=50
-XX:MaxMetaspaceFreeRatio=80加上:
检查是否重复创建 ClassLoader
是否大量动态代理未复用
三、GC 日志可视化分析工具(强烈推荐)
① GCeasy(最强可视化)
功能:
显示 pause 分布
显示 Old/Younng 区曲线
给出调优建议
自动检测泄漏风险
适合运维、开发一起使用。
② GCViewer
适合离线分析,优点:
老牌工具
图表稳定
能快速看出 MixedGC 触发是否正常
③ JDK 自带 jstat 命令(在线监控首选)
常用命令:
jstat -gcutil <pid> 1000 可以看到:
Young 区使用
Old 区变化
GC 次数
FullGC 次数
这对 事故期间 的快速判断非常重要。
四、不同堆大小对应的 G1 最佳调优模版(企业级)
🟦 1)堆大小 < 4G(常见 Web 服务)
-XX:+UseG1GC
-XX:MaxGCPauseMillis=150
-XX:InitiatingHeapOccupancyPercent=45
-XX:G1HeapRegionSize=4m
🟩 2)堆大小 4G–8G(常见中型系统)
-XX:+UseG1GC
-XX:MaxGCPauseMillis=120
-XX:InitiatingHeapOccupancyPercent=35
-XX:G1HeapRegionSize=8m
🟧 3)堆大小 8G–32G(大业务系统)
-XX:+UseG1GC
-XX:MaxGCPauseMillis=100
-XX:InitiatingHeapOccupancyPercent=25
-XX:G1HeapRegionSize=16m
-XX:G1ReservePercent=20
混合 GC 会更容易均匀触发。
🟥 4)堆大小 ≥ 32G(延迟不敏感、吞吐优先)
如果延迟敏感:优先 ZGC
如果吞吐优先:G1 + 更大 Region
五、什么时候要从 G1 切换到 ZGC?
满足任意两条即可考虑 ZGC:
✓ P99 要求 < 10ms
✓ 业务链路长,无法承受任何周期性抖动
✓ 堆 > 16G
✓ 并发线程数多
✓ FullGC 一直无法优化
ZGC 的减少停顿能力远在 G1 之上。
下一篇预告(第 6 篇)
第 6 篇我们将进入另一个重头戏:
**《Java 性能优化实战 30 讲》第 6 篇:
CPU 使用率飙高的根因定位(火焰图 + Linux 实战)
内容包括:
如何使用
top -H,pidstat,ps -L定位线程热点Java 内部 CPU 飙升的典型原因
线程死循环、锁竞争、GC 打满 CPU 的识别方式
火焰图(FlameGraph)完整排查流程
Java 服务 CPU 优化清单(生产级)
默认评论
Halo系统提供的评论