GC卡顿本质是STW暂停导致业务线程阻塞,需通过GC日志(抓Allocation Failure、Ergonomics等Cause)、jstat实时监控(盯O使用率>95%、FGC同步RT抖动)及堆dump(MAT查Retained Heap异常对象)联动定位根因。

线上系统卡顿,十有八九不是代码逻辑慢,而是 GC 在“悄悄暂停”你的业务线程。GC 日志就是最直接的行车记录仪——它不撒谎,只记录每次停顿、每次回收、每次内存变化。关键在于你会不会读。
盯住三类核心日志线索
打开 GC 日志(比如 [GC pause (G1 Evacuation Pause) (young), 0.045 secs]),先别急着看数字,重点抓括号里的“病因”:
- Allocation Failure:Eden 区撑不住了,说明对象分配太快,年轻代可能太小,或代码里高频创建短命对象(如循环中 new ArrayList()、StringBuilder)
- Ergonomics:JVM 自适应策略触发 Full GC,往往意味着老年代快满了,得立刻查老年代使用率是否持续 >80%
- Concurrent Mode Failure / Promotion Failed:CMS 或 G1 并发失败退化为 Full GC,常见于老年代碎片多、大对象(Humongous Allocation)频繁、或 Survivor 空间不足导致对象被迫提前晋升
用 jstat 快速验证趋势
日志是快照,jstat 是实时仪表盘。执行:
jstat -gcutil <pid> 1000(每秒刷新)
立即学习“Java免费学习笔记(深入)”;
重点关注这几列同步变化:
- S0/S1 使用率长期接近 0% 或 100%:Survivor 区太小,对象“熬不过一轮 GC”就晋升,加速老年代膨胀
- O(Old)持续 >95%:老年代告急,Full GC 随时爆发
- FGC 次数随 RT 抖动同步跳升:基本可锁定卡顿根源就是 Full GC
结合堆 dump 定位“真凶对象”
GC 日志告诉你“谁在捣乱”,堆 dump 告诉你“谁养的它”。在 Full GC 高发时段立即执行:
jmap -dump:live,format=b,file=heap.hprof <pid>
用 Eclipse MAT 打开后,按 Retained Heap 排序,重点关注:
- 静态 Map、ConcurrentHashMap 中 entry 数量持续增长(缓存未清理或 key 未实现 equals/hashCode)
- ArrayList、byte[]、String 占用异常高(可能是日志拼接、文件读取、JSON 序列化产生的临时大对象)
- 线程局部变量(ThreadLocal)持有大对象且未 remove(尤其在线程池复用场景下极易泄漏)
别只看次数,更要算“停顿成本”
很多新人只扫一眼“YGC 每小时 20 次,没 FGC”,就以为万事大吉。错!真正影响用户体验的是 STW 总耗时:
- 单次 Young GC 耗时从 15ms 涨到 60ms → 可能是 Survivor 区太小、对象晋升过早、或 Eden 区过大导致复制成本上升
- Full GC 耗时 >1s → 用户明显感知卡顿;>3s → 接口超时风险极高
- 观察 GC Cause 和 Pause Time 是否成对恶化(如连续出现 Allocation Failure + STW >50ms)→ 直接指向分配压力问题
不复杂但容易忽略:GC 卡顿从来不是孤立事件,它背后一定是内存分配模式和业务负载失配的结果。日志只是入口,顺着它往下挖,才能真正止住抖动。


















