标记-整理算法是兼顾内存利用率与碎片控制的折中方案,实现难点在于对象移动与引用更新,执行效率低于标记-清除但优于高存活率下的复制算法。

标记-整理算法的实现难度高于标记-清除,执行效率低于后者,但显著优于复制算法在高存活率场景下的表现。它不是单纯“标记+清除”的叠加,而是引入了对象移动与引用更新这一复杂环节,属于兼顾内存利用率与碎片控制的折中方案。
实现难点集中在对象移动与引用修复
标记阶段与标记-清除完全一致,依赖根可达性分析完成存活对象识别;真正增加复杂度的是整理阶段:
- 需重新计算每个存活对象的目标地址,并按顺序紧凑排列,不能破坏原有引用关系
- 必须遍历并更新所有指向这些对象的引用——包括Java栈帧中的局部变量、方法区的静态字段、寄存器中的临时引用,甚至JNI句柄表
- 移动过程需保证原子性,避免多线程环境下出现“悬空引用”或“部分移动”状态
- 若使用压缩式整理(如Serial Old),还需维护一个“偏移量映射表”辅助快速定位新地址
执行效率受存活对象数量和堆大小双重影响
时间开销主要分布在三块:标记耗时(与对象总数正相关)、整理耗时(与存活对象数量线性相关)、引用更新耗时(与引用数量强相关)。
- 标记阶段:与标记-清除基本持平,属O(n)复杂度
- 整理阶段:需重排对象布局,涉及大量内存拷贝,堆越大、存活对象越多,耗时越长
- 引用更新:是最大瓶颈之一,尤其在存在大量跨代引用或复杂对象图时,可能占整轮GC时间的40%以上
相比其他算法的效率定位清晰
它不追求低延迟,也不牺牲空间,而是为老年代“高存活、少变动”特性定制:
- 比标记-清除慢:因多了移动+更新步骤,Full GC停顿通常高出30%~60%
- 比复制算法快(在存活率>30%时):避免了全量复制开销,尤其适合老年代对象普遍长期存活的场景
- 内存分配效率反而更高:整理后形成连续空闲区,后续TLAB分配只需指针 bump,O(1)完成
实际工程中常通过分代与增量优化缓解压力
纯标记-整理极少单独使用,主流JVM会结合策略降低感知成本:
- Serial Old、Parallel Old默认采用该算法,但Parallel Old启用多线程标记与并行整理,缩短STW
- G1虽以Region为单位,但在Mixed GC后期若发现某些Region碎片严重,也会触发局部标记-整理式回收
- ZGC/Shenandoah等低延迟收集器放弃整理,改用读屏障+并发转移,本质上是用CPU换停顿,反向印证了整理本身的开销不可忽视










