标记-整理算法通过移动存活对象至一端并同步更新所有指针来消除外部碎片;分三轮扫描(标记与预计算地址、更新引用、移动对象),全程需STW以保证安全。

标记-整理算法在大型堆内存中并非“效率低下”的代名词,而是以可预测的移动开销换取长期稳定的分配能力——它不靠牺牲空间换速度,而是用一次整理换多次无碎片分配。
移动成本随堆增大而上升,但不是线性恶化
整理阶段需遍历所有存活对象并逐个迁移、更新引用,因此停顿时间(STW)确实随堆大小和存活对象数量增加。但关键在于:它只移动存活对象,而非全堆。在老年代高存活率(如70%~95%)场景下,实际移动量远小于复制算法需搬运的“全部存活”,也避免了标记-清除因碎片导致的反复GC。
- 若老年代10GB中8GB对象存活,标记-整理仅移动这8GB数据;复制算法则需双倍空间+搬运8GB,且无法复用原空间
- JVM会优化整理顺序(如按地址升序紧凑排列),减少缓存失效,提升迁移吞吐
- 现代回收器(如ZGC、Shenandoah)通过并发标记+增量整理,把大停顿拆成多个毫秒级暂停
碎片规避带来的间接效率收益更关键
大型堆一旦出现严重碎片,分配大对象(如缓存块、Netty直接内存、长序列化缓冲区)将频繁失败,触发额外Full GC甚至OOM。标记-整理从根源上消除离散空洞,使“总空闲 ≥ 请求大小”即可成功分配。
- 例如:堆中剩余512MB空闲,但分散为256个2MB碎块 → 大对象分配失败;整理后合并为单块512MB → 一次命中
- 避免因碎片引发的连锁GC,减少总体GC次数和累计停顿时间
- 对长时间运行的服务(如金融交易系统、实时推荐引擎),稳定性比单次GC快几毫秒更重要
与替代方案对比:没有免费午餐,只有权衡取舍
在超大堆(数十GB至TB级)中,纯标记-整理确实可能被更细粒度方案替代,但其设计逻辑仍在底层延续:
- G1不是放弃整理,而是按Region局部标记+选择性压缩(只整理垃圾多的Region),本质是“分治版标记-整理”
- ZGC通过着色指针和读屏障实现对象移动与用户线程并发,移动本身仍发生,只是不阻塞应用
- 单纯用标记-清除+外部碎片管理(如buddy allocator)在JVM堆内难以高效落地,易引入复杂性和额外开销
调优建议:让整理更聪明,而非更少
不追求禁用整理,而是降低其影响范围和频率:
- 合理设置老年代初始大小与最大值,避免频繁扩容触发Full GC
- 监控Old Gen存活率(如jstat -gc输出中的OU/OC比值),若长期>90%,说明对象确实该常驻,整理收益大于成本
- 对已知生命周期长的大对象(如静态配置缓存),考虑使用-XX:+UseLargePages或堆外内存,减轻整理压力
- 启用-XX:+ClassUnloadingWithConcurrentMark(JDK17+)等特性,配合类卸载减少老年代污染

















