STW是JVM垃圾回收中为保障内存安全而强制暂停所有应用线程的必然机制,根本原因在于需冻结引用关系以防漏标或错标,并确保对象移动等操作的排他性,不同GC阶段(如初始标记)的STW时长与位置差异显著。

STW(Stop-The-World)不是“偶尔卡一下”的小问题,而是 JVM 垃圾回收过程中为保障内存安全而强制暂停所有应用线程的必然机制。它直接决定系统能否满足低延迟、高响应的要求——尤其在交易、实时通信或高并发服务中,一次几十毫秒的停顿就可能触发超时、降级甚至雪崩。
STW 为什么不可避免?
根本原因在于堆内存是所有线程共享的资源,而 GC 必须保证两个关键前提:
- 引用关系冻结:标记存活对象时,若用户线程同时修改引用(如赋值、断引用、新建对象),会导致漏标(本该活的对象被回收)或错标(已死对象被保留);
- 内存操作排他:移动对象、更新指针、压缩空间等操作,必须杜绝应用线程读写同一地址,否则会访问迁移中无效地址,引发崩溃或数据错乱。
这就像银行金库清点现金时,必须暂停所有存取款业务——不是不想服务,而是安全底线不可妥协。
不同 GC 阶段的 STW 表现差异大
STW 并非全程“一刀切”,其持续时间与发生位置高度依赖 GC 算法和当前阶段:
- 初始标记(Initial Mark):仅扫描 GC Roots(如栈帧局部变量、静态字段),耗时极短(通常
- 重新标记(Remark):修正并发标记期间因用户线程运行导致的偏差,是 G1/CMS 中最耗时的 STW 环节,常占整个 GC 停顿的 70% 以上;
- Evacuation(G1)或 Relocate(ZGC):G1 的对象复制、ZGC 的重映射阶段虽主打并发,但仍需短暂 STW 扫描根区域或更新转发指针;
- Full GC 或元空间回收:涉及全堆扫描+类卸载,停顿可达数百毫秒,应严格避免。
真正影响停顿时长的,不只是堆大小
很多人误以为“堆越大,STW 越长”,但实际瓶颈常来自应用行为本身:
- 跨代引用爆炸:老年代对象大量持有新生代引用(如缓存池长期持有一个临时 DTO),导致 G1 的 Remembered Set 维护开销剧增,拖慢 Evacuation;
- 隐式晋升频繁:Survivor 区过小或 MaxTenuringThreshold 过高,使本该在 Eden 回收的对象提前进入老年代,加剧混合回收压力;
- GC Roots 膨胀:大量 ThreadLocal 存储大对象、日志框架无节制绑定 MDC、反射频繁生成 ClassLoader,都会显著拉长初始标记时间。
优化不能只靠换收集器
ZGC 和 Shenandoah 确实将 STW 控制在 10ms 内,但它们对 JDK 版本(≥11)、操作系统支持(Linux x64/AArch64)和堆大小(ZGC 推荐 ≥8GB)有明确要求。更务实的路径是分层调优:
- 代码层:复用 StringBuilder、避免循环内 new Map、用 weakReference 管理缓存;
- 参数层:G1 下设 -XX:MaxGCPauseMillis=30(非硬性承诺,而是调度目标),配合 -XX:G1HeapRegionSize 调整以减少跨区引用;
- 监控层:用 -Xlog:gc+phases 启用阶段级日志,定位是 Root Scan 慢,还是 Remark 耗时高,再针对性优化。

















