cms标记阶段的stw仅发生在初始标记和重新标记两个环节,其中remark耗时更长、更易成瓶颈;优化核心是缩短stw时间、减少扫描对象量、降低并发干扰,初始标记本身极轻量,只遍历gc roots直连对象,不深入对象图。

CMS收集器中,标记阶段的STW(Stop-The-World)仅出现在初始标记(Initial Mark)和重新标记(Remark)两个环节,其中Remark阶段耗时更显著、更易成为性能瓶颈。优化核心不是消除STW,而是缩短其持续时间、减少需扫描对象数量、降低并发干扰影响。
初始标记阶段的轻量级优化
该阶段本身极快——只遍历GC Roots及其直接引用对象(如线程栈、静态变量、JNI句柄等),不深入对象图。JDK 8起默认已启用并行化,可通过参数显式强化:
- -XX:+CMSParallelInitialMarkEnabled:强制开启并行初始标记,多核CPU下进一步压缩停顿(通常
- 确保GC Roots本身不臃肿:避免过多静态集合缓存、长生命周期ThreadLocal或未清理的JNI全局引用,否则Root扫描会变慢
- 不建议人为干预此阶段——它本就不是瓶颈,过度调优收益微乎其微
重新标记阶段的关键优化路径
Remark是CMS STW耗时主力,因需修正并发标记期间用户线程引发的引用变动(如新生代对象晋升、老年代引用更新)。优化重心在于“减量”与“预处理”:
- 强制前置Minor GC:启用 -XX:+CMSScavengeBeforeRemark,在Remark前触发一次新生代回收。此举大幅减少需跨代扫描的年轻代存活对象数量,实测可将200ms+停顿降至20ms内
- 控制Eden区填充节奏:配合 -XX:CMSScheduleRemarkEdenSizeThreshold(默认2MB)和 -XX:CMSScheduleRemarkEdenPenetration(默认50%),让预清理在Eden使用达阈值时启动,避免Remark时Eden已接近满载
- 限制并发预处理时长:设置 -XX:CMSMaxAbortablePrecleanTime=5000(单位毫秒),防止预清理无限等待Minor GC,超时即进入Remark,保障响应确定性
卡表与记忆集协同降低扫描范围
CMS依赖卡表(Card Table)记录老年代对新生代的写操作,但卡表粒度粗(通常512B一页),易导致“假脏卡”。优化需兼顾精度与开销:
- 保持默认卡表机制即可,无需手动调整;重点是确保 -XX:+UseCompressedOops 开启(JDK7u4后默认),减小对象头尺寸,间接降低卡表更新频率
- 避免在老年代大量创建强引用指向新生代对象的结构(如缓存中存了刚new出的DTO),这类跨代引用会放大Remark扫描压力
- 监控 remark time 和 preclean time 日志指标,若preclean耗时占比过高,说明卡表污染严重,应检查业务代码中的跨代引用模式
规避Remark膨胀的架构级建议
参数调优有边界,根本解法在于降低对象图动态性:
- 减少长生命周期对象持有的短期对象引用(例如Service类持有Request上下文)
- 用弱引用/软引用替代强引用缓存,降低跨代引用强度
- 对高写入场景(如实时日志聚合),考虑将中间状态下沉至堆外内存或本地缓存,避开JVM堆引用链











