标记-整理算法与标记-清除算法均基于可达性分析标记存活对象,但后续处理不同:前者将存活对象紧凑排列并更新引用,消除碎片但stw时间长;后者直接释放未标记对象内存,产生碎片但停顿较短。

标记-整理算法和标记-清除算法都基于“可达性分析”识别存活对象,但后续处理逻辑完全不同——前者重排内存,后者直接释放空间。选择哪个,关键看是否愿意为内存连续性多花时间。
标记阶段完全一致
两种算法在第一步完全相同:从 GC Roots(如栈帧变量、静态字段、JNI 引用等)出发,遍历所有可达对象,并在对象头或独立位图中标记为“存活”。未被标记的对象即为垃圾。这一步不涉及内存移动或引用更新,开销基本相当。
清除 vs 整理:本质分叉点
标记完成后,二者走向截然不同的路径:
- 标记-清除:遍历整个堆,定位所有未标记的内存块,将其加入空闲链表(Free List)。对象位置不变,引用无需修正;但空闲空间呈离散分布。
- 标记-整理:先计算每个存活对象的新地址(通常向内存起始端紧凑排列),再逐个复制对象到目标位置;最后更新所有指向这些对象的引用(包括栈帧、寄存器、其他堆对象中的指针);最终清空边界外全部内存。
对运行时的影响差异显著
这些实现差异直接决定系统行为:
- STW 时间:标记-整理必须全程暂停用户线程,因为移动对象和修正引用不能与程序并发进行;标记-清除仅需暂停标记和清除两个阶段,中间可部分并发(如 CMS 的并发清除)。
- 内存分配效率:整理后堆是连续的,新对象分配只需递增指针(bump-the-pointer),O(1);清除后依赖空闲链表查找合适块,最坏 O(n),尤其对大对象易失败。
- 空间管理成本:标记-清除需维护空闲链表或位图,且碎片可能触发额外 GC;标记-整理无碎片,但需额外空间暂存移动中的对象(虽不预留整块,但复制过程有临时占用)。
典型实现与调优体现
实际 JVM 并非简单二选一,而是按区域和场景动态适配:
- Serial Old 默认用标记-整理,因单线程下整理开销可控,且老年代对象存活率高,碎片危害大;
- CMS 主流程用标记-清除保低延迟,但通过 -XX:+UseCMSCompactAtFullCollection 在 Full GC 时强制整理;
- G1 不主动整理,但当 Region 碎片化严重时,会退回到 Full GC 并启用标记-整理;
- ZGC 和 Shenandoah 则通过读屏障+转发指针,在几乎不停顿的前提下完成并发整理,绕开了传统实现的 STW 瓶颈。










