标记-整理算法通过将存活对象紧凑移至一端并清理边界外空间,使堆内存规整、空闲区连续,从而为指针碰撞提供必要前提;此时jvm只需原子移动分界指针即可完成分配。

标记-整理算法本身不直接等同于指针碰撞,但它为指针碰撞创造了前提条件——堆内存规整。真正执行分配动作的,是JVM在规整后的堆上启用的指针碰撞机制。
为什么标记-整理后能用指针碰撞
标记-整理算法分两步:先标记所有存活对象,再将它们全部向内存一端(如起始地址)紧凑移动,最后清理边界外的废弃空间。这一过程消除了碎片,使已用内存与空闲内存严格分界,中间形成一条清晰的“分界指针”。这正是指针碰撞唯一依赖的物理基础。
- 整理前:内存中存活对象散落各处,空闲块大小不一、位置随机 → 只能靠空闲列表逐个查找
- 整理后:所有存活对象紧挨着排在低地址侧,高地址侧是一整块连续空闲区 → 指针只需向右平移对象大小即可完成分配
- 典型场景:Serial Old、Parallel Old 收集器的老年代回收后,常采用指针碰撞分配新晋升对象
指针碰撞在标记-整理堆中的实际行为
分配不是简单“挪指针”,而是一套受控的原子操作:
- 分配请求到达时,JVM检查剩余空闲空间是否 ≥ 对象所需字节数;不足则触发下一次GC
- 确认足够后,通过CAS(Compare-and-Swap)原子更新全局分配指针,避免多线程竞争错位
- 实践中几乎总配合TLAB(Thread Local Allocation Buffer):每个线程独占一小段Eden或老年代空间,先在本地指针上 bump,满后再同步全局指针,大幅减少CAS争用
- 分配完成后,对象头写入、字段初始化等后续动作由解释器或JIT完成,与分配机制解耦
它和复制算法下的指针碰撞有何不同
虽然都用指针碰撞,但底层内存结构逻辑不同:
- 复制算法(如ParNew年轻代):每次GC后,From区清空,To区变成全新规整空间,指针从0开始 bump —— 是“重建式”规整
- 标记-整理算法(如Serial Old老年代):不切换区域,而是原地压缩迁移,保留原有对象相对顺序,指针在原有地址空间内推进 —— 是“就地压实式”规整
- 因此,标记-整理后的指针碰撞更依赖内存地址连续性,对大对象分配更敏感;若剩余空闲段不够大,即使总空闲量充足,也无法使用指针碰撞,会退化为空闲列表
什么情况下会失效或降级
即使刚做完标记-整理,指针碰撞也不总是可用:
- 禁用TLAB(-XX:-UseTLAB)且并发线程多 → 全局指针CAS失败率升高,JVM可能临时启用空闲列表兜底
- 分配超大对象(如长度超阈值的数组),超出当前空闲段容量 → 直接跳过指针碰撞,查空闲列表找合适块
- G1收集器在老年代虽有整理能力,但默认按Region管理,不追求全局规整 → 即使局部Region被整理,整体仍走空闲列表或分区式分配,而非传统指针碰撞
- ZGC、Shenandoah等低延迟收集器,因并发整理无法保证瞬时全局规整,也基本不依赖指针碰撞分配










