减少jvm gc stw时间的关键是使回收策略与应用行为对齐:优先选用g1并设合理停顿目标,固定堆大小,调控新生代比例与region尺寸,规避生成大量难回收对象的代码陷阱,并通过日志与监控验证效果。

减少 JVM 垃圾回收过程中的 STW 时间,核心不是堆越大越好,也不是盲目换新 GC,而是让回收策略与应用行为对齐。关键在于选对回收器、配准参数、管住代码习惯。
优先用 G1 并设准停顿目标
G1 是目前 JDK 8–17 主流低延迟场景的务实选择,ZGC/Shenandoah 虽强但对 JDK 版本和大堆(>8GB)更敏感,且需额外验证稳定性。
- -XX:+UseG1GC 必须显式开启,不能依赖默认
- -XX:MaxGCPauseMillis=200 设为软目标(如 200ms),太低(如 50)会触发更频繁 Young GC,反而抬高平均停顿;太高(如 500)使 G1 失去意义
- -Xms 和 -Xmx 必须相等,否则 G1 无法稳定 Region 划分,易引发 Concurrent Mode Failure(退化为 Full GC)
控制新生代弹性与区域大小
Young GC 停顿主要取决于存活对象复制量和卡表扫描开销,跟 Region 管理效率直接相关。
- -XX:G1NewSizePercent=20 -XX:G1MaxNewSizePercent=40:避免新生代过小(如 5%)导致频繁伸缩,也避免过大(如 60%)挤压老年代并发标记空间
- -XX:G1HeapRegionSize=1M(或 2M):若业务常见对象 >512KB(如大缓存 DTO、图片 buffer),设大 Region 可减少跨区引用,降低 Remembered Set 维护成本
避开拖长 STW 的典型代码陷阱
很多 STW 延长不是 GC 配置问题,而是应用层“悄悄喂给 GC 大量难处理对象”。
- 避免在循环中反复
new StringBuilder().append(...).toString(),生成大量临时大字符串,容易直接进入老年代 - 慎用
ThreadLocal<map></map>存放非轻量级对象,线程复用下易滞留,GC 扫描整个 ThreadLocalMap 开销不小 - Logback 中
%X{traceId}若每次请求都 new 新 MDC Map,会显著推高年轻代分配速率,间接增加 Young GC 频次 - 禁用 -XX:+UseStringDeduplication(除非确认有海量重复字符串),它会在 GC 期间加锁扫描,延长 STW
验证是否真见效,别只看平均值
平均停顿时间掩盖毛刺。高频短停顿比低频长停顿更伤用户体验。
- 用
jstat -gc -h10 <pid> 1000</pid>持续采样,观察 G1YGC/G1FGC 的频次与单次耗时波动 - JDK 10+ 推荐开启详细日志:
-Xlog:gc*,gc+phases:file=gc.log:time,tags,重点关注 Evacuation 阶段里 Root Region Scan 和 Remembered Set 处理耗时 - 若 Mixed GC 日志中 regions 回收数常超总 Region 数的 20%,说明混合回收压力过大,可试调
-XX:G1MixedGCCountTarget=8拆分工作量











