6.2 GC 停顿与收集器选型 本节摘要:P99 毛刺的元凶常常是 GC 长停顿。本节从一次接口毛刺复盘讲起,学会读 GC 日志判断停顿根因(哪种 GC、多长、为什么),理清分代收集器与 G1/ZGC 的设计取舍,掌握对象分配层面的"少产垃圾"优化,避免"调参玄学"。 事故现场:每 40 秒一次的 P99 尖刺 支付回调接口的 P99 曲线上每隔约 40 秒出现一次尖刺,从 80ms 跳到 1.2s。机器 CPU、下游依赖全部正常。打开 GC 日志( ),规律一目了然: Young GC 只有 23ms 无伤大雅,Full GC 950ms 才是尖刺本尊——STW(stop-the-world)期间所有请求冻结,落在窗口内的请求 P99 就被抬上去。
本节摘要:P99 毛刺的元凶常常是 GC 长停顿。本节从一次接口毛刺复盘讲起,学会读 GC 日志判断停顿根因(哪种 GC、多长、为什么),理清分代收集器与 G1/ZGC 的设计取舍,掌握对象分配层面的"少产垃圾"优化,避免"调参玄学"。
支付回调接口的 P99 曲线上每隔约 40 秒出现一次尖刺,从 80ms 跳到 1.2s。机器 CPU、下游依赖全部正常。打开 GC 日志(-Xlog:gc*),规律一目了然:
[gc] GC(412) Pause Young (Normal) 2G->260M(4G) 23ms [gc] GC(413) Pause Full (Ergonomics) 3G->800M(4G) 950ms
Young GC 只有 23ms 无伤大雅,Full GC 950ms 才是尖刺本尊——STW(stop-the-world)期间所有请求冻结,落在窗口内的请求 P99 就被抬上去。触发原因是 Ergonomics:老年代占用逼近阈值,JVM 自动发起 Full GC。为什么老年代涨这么快?进一步看对象晋升:回调处理里每请求创建约 2MB 的临时报表对象(第 4 章的字符串拼接同款问题),Young 区放不下直接晋升老年代(大对象规则),死对象全堆在老年代等 Full GC 收尸。
这个案例说明 GC 调优的典型层次:表象(尖刺)→ 直接原因(Full GC 长)→ 结构原因(垃圾分配模式)。只对着收集器参数使劲,第三层不解决,参数怎么调都是按下葫芦浮起瓢。
2G->260M 表示回收效果;回收后如果每次都比上次高(谷底抬升),就是第 2/6.1 章讲过的泄漏曲线JDK 8 用 -XX:+PrintGCDetails,JDK 9+ 统一为 -Xlog:gc*:file=gc.log:time,uptime:filecount=5,filesize=20m。日志之外,jstat -gcutil pid 1000 一秒一个快照,看各代占用与 GC 次数的时间趋势,适合没有历史日志的现场。
分代收集器(Serial / Parallel / CMS,JDK 8 默认 Parallel):按分代假说组织(第 6.1 节),Parallel 吞吐优先、STW 长而稀疏,适合批处理;CMS 以并发标记换短停顿,但标记-清除留碎片,碎片满了一样退化成 Full GC(还被 JDK 14 正式移除)。
G1(JDK 9+ 默认):把堆切成等大的 Region(1-32MB),不再物理隔离新老年代,而是逻辑标记哪些 Region 扮演什么角色。回收按收益排序收集 Region(Garbage First 名字的由来),并且尊重用户给的停顿目标 -XX:MaxGCPauseMillis=200——G1 会估算每个 Region 回收要多久,优先挑"垃圾占比高、回收快"的做。大对象(超过 Region 一半)走专门的 Humongous 区域,大对象多时 G1 表现劣化,这解释了为什么"换成 G1 反而更慢"的案例十有八九是分配了海量几 MB 的临时对象。
ZGC / Shenandoah(JDK 15+ / 21 转正):着色指针 + 读屏障实现并发整理,停顿与堆大小解耦,亚毫秒到几毫秒。代价是吞吐损 5-15%、需要更多内存做转发。适用:堆超大(几十 GB 以上)、延迟极度敏感(交易、实时风控)。

GC 优化容易滑向玄学,守住顺序就稳:
-Xmn 或 G1 让 JVM 自适应)、G1 的 Region 大小与停顿目标回到事故案例的修复:报表对象改为流式生成(单次请求内存 2MB→200KB),Young 区装得下、就地回收,晋升消失,Full GC 从 40 秒一次降到基本不出现——没动一个 JVM 参数。
⚠️ 常见坑:把
-XX:MaxGCPauseMillis设成 10ms 以为立竿见影。停顿目标收紧的代价是每次回收的 Region 更少、频率更高、吞吐下降,且目标本身低于收集器能力时 JVM 只能尽力而为——数字不等于承诺。
💡 关键直觉:GC 调优调的是"垃圾的产生与死亡节奏",不是玄学参数。看懂分配速率与晋升曲线,比背十个参数组合有用。
下一节进入类加载的世界:同一个类被加载两次会怎样。