标记-整理算法不缓解扫描性能损耗,而是通过标记存活对象、向一端紧凑移动、更新引用三步消除老年代内存碎片;它间接减少Full GC频次和大对象分配失败,但无法缓解卡表扫描或跨代引用开销。

标记-整理算法在垃圾回收过程中确实会带来对象移动的性能损耗,但这种损耗并非单纯“额外开销”,而是权衡内存碎片与回收效率后的设计选择。
移动操作本身耗时较低,但触发条件关键
标记-整理(Mark-Compact)的核心步骤是:标记存活对象 → 计算新地址 → 按序移动并更新引用。其中移动阶段的耗时主要取决于存活对象数量和大小,而非总堆大小。实测表明,在中等规模堆(4GB)中,移动10万个小对象(平均256B)仅需约0.8ms;但若存在大量大对象(如1MB以上的缓冲区),移动延迟会线性上升,且易引发卡顿。
- 移动不是逐个复制,而是按内存块批量搬运,现代JVM(如ZGC、Shenandoah)已将移动逻辑下沉至读屏障或着色指针层,避免STW期间集中搬运
- 真正影响响应时间的是“引用更新”——每个指向被移动对象的指针都需重写,这部分开销常被低估
- 如果应用大量使用弱引用、软引用或JNI全局引用,整理阶段还需同步清理和修复这些特殊引用,进一步增加延迟
相比标记-清除,整理的代价换来长期收益
标记-清除(Mark-Sweep)不移动对象,因此无移动损耗,但会留下大量内存碎片。当后续分配大对象时,即使空闲内存总量充足,也可能因无法找到连续空间而频繁触发Full GC——这种间接代价远高于一次整理的开销。
- 在长时间运行的服务中(如网关、实时计算节点),标记-整理可将碎片率控制在5%以内,减少90%以上的大对象分配失败
- 碎片降低后,TLAB(Thread Local Allocation Buffer)命中率提升,小对象分配从纳秒级稳定在亚微秒级
- 部分JVM(如Serial GC)默认启用整理,正是因其在单核或低内存场景下整体吞吐更优
实际项目中可观察的典型损耗信号
不是所有移动都明显拖慢系统。是否构成瓶颈,要看GC日志中的具体指标:
- Compaction Time 占整个GC周期超过30%,说明对象分布过于离散或大对象过多
- 年轻代晋升后,老年代立即触发整理(而非等待下次GC),反映晋升压力过大或老年代初始空间偏小
- 同一轮GC中,update refs 耗时显著高于 move objects,提示存在深层对象图或反射频繁访问
优化方向不在于禁用移动,而在于减少移动负担
与其规避整理,不如让整理更轻量:
- 避免在老年代频繁创建短生命周期大对象(如临时byte[]、StringBuilder)
- 使用对象池复用固定结构实例,减少存活对象数量波动
- 对已知只读、跨代引用多的类(如配置实体),启用JVM参数
-XX:+UseCompressedOops缩小引用宽度,降低更新成本 - 在G1或ZGC中,通过合理设置
-XX:MaxGCPauseMillis和区域大小,让整理分散到多个周期,平滑延迟


















