复制算法能缩短单次stw,因其仅需扫描eden和一个survivor区,将约2%存活对象顺序复制到另一survivor区后批量清空源区,避免标记-清除遍历和标记-整理移动开销,操作原子、无碎片、cpu缓存友好。

新生代复制算法本身不能消除 Stop-The-World(STW),但它通过设计优化,显著降低了单次 STW 的持续时间。关键在于:它只处理“朝生夕死”的对象,存活率低、复制量小、清理动作原子且无碎片,从而让 GC 线程在极短时间内完成整个 Minor GC 流程。
为什么复制算法能缩短单次 STW
新生代中约 98% 的对象在第一次 GC 时就已不可达。复制算法只需扫描 Eden 和一个 Survivor 区,把少量存活对象复制到另一块 Survivor 空间,然后直接清空源区域——这个过程没有标记-清除的遍历开销,也没有标记-整理的内存滑动成本。所有操作是顺序写、批量清,CPU 缓存友好,执行路径短。
- 不需处理内存碎片,省去整理阶段耗时
- 复制对象数量少(通常
- 算法逻辑简单,JVM 实现高度优化(如使用卡表辅助记忆集)
- 无需更新大量跨代引用,仅需处理少量从老年代指向新生代的“脏卡”
影响 STW 时间的关键配置因素
即使算法高效,若堆结构或参数不合理,仍会拉长 STW。重点不在“用不用复制算法”,而在“怎么用好”:
- 新生代大小要合理:过大则每次 Minor GC 扫描/复制范围大;过小则 GC 频繁,总停顿时间上升。建议初始值设为堆总内存的 1/4~1/3,并结合 -Xmn 显式指定
- Eden:Survivor 比例要匹配实际存活率:HotSpot 默认 8:1,但若应用对象平均存活 2–3 次才晋升,可尝试调大 Survivor(如 -XX:SurvivorRatio=6),避免过早触发担保失败(Promotion Failure)导致 Full GC
- 避免显式触发 GC:调用 System.gc() 会强制一次 Full GC,STW 时间远超 Minor GC,必须禁用
- 减少 GC Roots 扫描压力:局部变量过多、线程栈过深、JNI 引用频繁,都会延长安全点到达时间和根扫描时间
配合复制算法的运行时优化手段
复制算法的效率还依赖 JVM 如何快速达成一致快照并执行回收:
- 安全点(Safepoint)位置要密集:方法调用、循环边界、异常抛出点等都是天然安全点。避免写超长无分支的计算逻辑,否则线程可能长时间无法进入安全点,拖慢 STW 启动
- 开启 GC 日志分析瓶颈:添加 -Xlog:gc*,gc+phases=debug -XX:+PrintGCDetails,重点关注 “Pause Init” 到 “Pause End” 的耗时,以及 “Root scanning”、“Object copy”、“Code cache GC” 等子阶段占比
- 考虑 ParNew 或 G1 的并行复制能力:Serial 使用单线程复制;ParNew 可用多线程并行复制 Eden+Survivor,进一步压缩 STW;G1 在 Young GC 中也采用复制+并行,且 Region 粒度更细,暂停更可控
注意复制算法的边界限制
它只解决新生代问题,无法规避 STW 本质。以下情况仍会导致 STW 延长甚至失控:
- Survivor 空间不足,大量对象直接晋升老年代,可能触发老年代 GC(如 CMS Concurrent Mode Failure 或 G1 Evacuation Failure)
- 大对象(>Region 一半)直接分配在 Humongous 区,绕过复制逻辑,且其回收常伴随额外 STW
- 元空间(Metaspace)扩容失败、类卸载复杂、JNI 全局引用清理等非堆操作也会引发 STW











