标记-整理算法核心是可控有序批量移动存活对象并更新引用:先从gc roots精准标记所有可达对象,再按地址顺序滑动压缩至堆一端,保持局部性且单次遍历计算新地址,最后集中修正引用,兼顾碎片消除与开销控制。

标记-整理算法处理大量对象移动时,核心不是“避免移动”,而是用可控、有序、批量的方式完成移动,同时确保引用一致性。它不追求零开销,而是通过结构化步骤把移动成本控制在可接受范围内。
移动前先精准标记存活对象
移动操作只作用于真正存活的对象,避免无效搬运。标记阶段从 GC Roots(如栈变量、静态字段、JNI 引用)出发,递归遍历所有可达对象并打标。大对象、小对象、长期存活对象一视同仁,只要可达就进入移动队列。这一步直接决定了后续移动的范围——标记越准,移动越少;若漏标,会导致对象被误回收;若错标,则徒增移动负担。
按地址顺序紧凑滑动,不打乱相对位置
整理阶段采用“滑动式”(sliding compaction),而非随机重排。JVM 将所有已标记对象按原始内存地址升序排列,整体向堆起始端(如低地址)平移,中间不留空隙。这种顺序移动有两大好处:
- 保持对象原有空间局部性:相邻对象仍大概率保留在同一缓存行或内存页,减少 CPU 缓存失效
- 支持单次遍历计算目标地址:无需额外存储映射表,每个对象的新地址 = 当前累计偏移量 + 原大小,实时推导
批量更新引用,避免逐个修正
对象移动后,所有指向它的引用必须同步更新,否则程序将访问错误地址。JVM 不是挨个扫描栈或字段,而是:
- 在整理过程中维护一个“转发指针”或“位移偏移量”,记录每块连续存活区的整体位移
- 对栈帧、静态区、对象字段等引用源进行集中扫描,仅修正那些指向已移动区域的引用
- 部分收集器(如 G1)还利用卡表(Card Table)缩小扫描范围,只检查可能含跨代引用的内存页
针对大对象和高存活率场景做适配
当堆中大量对象长期存活(如缓存、配置、大数组),全堆整理开销会上升。现代 JVM 会主动规避全量移动:
- G1 改为“部分整理”:只选择若干 Region(区域)进行复制与整理,其余区域暂不处理,平衡停顿与碎片控制
- ZGC/Shenandoah 使用读屏障+并发整理:移动与用户线程并行执行,引用更新延迟到首次访问时完成,STW 时间压至毫秒级
- Serial Old / Parallel Old 等则接受较长时间 STW,但通过多线程并行移动对象,缩短实际暂停窗口











