标记-清除算法是JVM最基础的垃圾回收逻辑,先标记所有存活对象,再清除未标记对象,不移动对象但产生内存碎片;复制算法用于新生代,通过复制存活对象到新区域并整块清空原区域实现无碎片高效回收;标记-整理则在老年代中先标记后将存活对象向一端压缩,消除碎片且不浪费空间。

标记-清除:老年代的基础清理方式
这是最原始也最直接的清除逻辑。它分两步走:
— 标记阶段:从 GC Roots(如栈帧里的局部变量、静态字段、JNI 引用等)出发,递归遍历所有可达对象,在对象头打上“存活”标记;
— 清除阶段:再次扫描整个老年代内存区域,把没被标记的对象所占空间视作“可回收”,直接加入空闲列表(Free List),不真正清零内存内容,只更新元数据和指针。
这种逻辑不移动对象,停顿时间相对可控,但会留下大量不连续的小块空闲空间——也就是内存碎片。当后续需要分配大对象时,即使总空闲足够,也可能因无法找到连续空间而提前触发 Full GC。
复制算法:新生代的高效“搬迁式”清除
它不靠“清除”旧空间,而是靠“转移”存活对象来间接完成清理:
— Eden 区满时触发 Minor GC;
— 扫描 Eden 和一个 Survivor 区(如 S0),把所有存活对象复制到另一个空的 Survivor 区(如 S1);
— 原 Eden + S0 区整块内存直接清空(指针归零或重置为起始位置),无需逐个判断。
这种逻辑天然无碎片,分配新对象只需移动一个指针(Bump-the-pointer),极快。但代价是必须预留一半空间备用——现代 JVM 通过 Eden:Survivor=8:1:1 的三区设计,把空间浪费压缩到约 10%,兼顾效率与利用率。
标记-整理:老年代的碎片治理方案
当老年代碎片严重、又不适合频繁复制时,就启用标记-整理逻辑:
— 先标记所有存活对象(同标记-清除);
— 再将它们全部向内存一端(通常是起始地址)紧凑排列;
— 最后直接清空边界之后的所有空间。
这相当于一次“内存压实”,既避免了碎片,又不需要额外空间。但移动对象意味着要更新所有引用地址(需 Remember Set 或 Card Table 协助),带来额外开销,所以通常只在 CMS 失败后或 G1 的 Full GC 阶段启用。
现代收集器的清除逻辑更偏向“增量+并发”
比如 G1 和 ZGC 已不依赖全局 Stop-The-World 的完整标记-清除流程:
— G1 把堆划成多个 Region,每次只清理部分垃圾最多的 Region,清除动作在暂停中完成,但标记过程可并发执行;
— ZGC 使用着色指针(Colored Pointer)和读屏障,在应用线程运行时就完成对象标记与重定位,清除逻辑隐含在对象迁移和内存页回收中,几乎不造成明显停顿。
它们的“清除”不再是传统意义上的批量扫描,而是与对象生命周期管理、内存页映射、并发协调深度耦合的系统行为。

















