响应感强的服务应关注单次GC停顿的稳定性而非平均值,G1的MaxGCPauseMillis仅为目标非保证;吞吐型任务优先选Parallel GC;ZGC低延迟需满足分配平稳等前提;变量密集型宜调大新生代并禁用System.gc()。

直接说结论:响应感强的服务(比如用户点击后要立刻反馈的API、实时推荐、网关),核心看单次停顿是否稳定可控,而不是“平均值低”或“参数设了就生效”。选GC收集器不是比谁名字新,而是看它能不能把STW压在业务可容忍的毛刺范围内。
先盯住真实停顿,别被参数骗了
很多人给G1加-XX:MaxGCPauseMillis=100,就以为每次GC一定≤100ms。其实这只是G1的“努力目标”,不是承诺值。实际中它可能因大对象晋升、并发标记未完成、Humongous Region频繁分配等原因,突然打出200ms甚至更长的停顿。
- 必须开启详细GC日志:-Xlog:gc*:file=gc.log,重点看每一行Pause Young (Mixed)和Pause Full的实际耗时
- 用jstat -gc
1s 观察停顿分布,关注P95/P99停顿,不是平均值 - 如果发现某次停顿远高于设定值,说明这个参数已失效,该调策略,不是再压数值
吞吐量型任务别硬上G1或ZGC
后台批处理、定时导出、离线计算这类任务,目标是“一小时跑完1000个任务”,允许中间卡顿几十毫秒。Parallel GC全程STW但效率高、无协调开销,反而比G1更稳更快。
- Parallel GC默认启用(JDK8+),配-XX:+UseParallelGC即可,不用额外调参
- 堆>8GB且CPU≥8核时,建议设-XX:ParallelGCThreads=物理核数×5/8,避免线程争抢
- G1在纯吞吐场景下可能拖慢总耗时——它的并发标记、Region管理本身有CPU和内存开销
真要低延迟,ZGC/Shenandoah不是万能解药
ZGC宣传“停顿与堆大小无关”,前提是分配速率平稳、没突发大对象、元空间不卡顿。现实中,很多“ZGC停顿突增”问题,根源不在ZGC本身,而在:
- 安全点同步延迟(比如应用线程长时间执行native代码或循环)
- Reference处理积压(大量WeakReference/PhantomReference未及时清理)
- 类卸载慢(动态加载类多、未触发类回收)
- 堆外内存或Direct Buffer泄漏,间接拖慢GC节奏
变量处理密集型场景的实操建议
如果应用特点是高频创建短生命周期对象(如JSON解析、规则引擎每请求生成百个临时DTO),属于典型的“变量处理密集型”,建议:
- 新生代调大(比如占堆40%~50%),减少Minor GC频次;Eden区越大,对象越可能在Minor GC时自然消亡
- Survivor Ratio保持默认(8),避免过小导致提前晋升到老年代
- 优先试Parallel GC + 调优新生代,稳定后再对比G1(开-XX:+PrintGCDetails看Evacuation Pause是否抖动)
- 禁用System.gc(),避免人为触发Full GC打乱节奏

















