JVM 垃圾回收有哪些算法?各自的优缺点是什么?

结论先行:三大基础算法是标记-清除、复制、标记-整理。现代收集器从不单用一种,而是分代混合:新生代存活率低用复制算法,老年代存活率高用标记-清除或标记-整理,兼顾吞吐、停顿与碎片控制。

三种算法对比

算法思路优点缺点
标记-清除先标记垃圾再统一清除实现简单、不搬移对象产生内存碎片,大对象可能无处安放
复制存活对象整体复制到另一块空间无碎片、分配只需移动指针浪费空间(或需分配担保);存活率高时代价大
标记-整理标记后把存活对象向一端移动无碎片、空间利用率高移动对象需更新引用,停顿更长

分代视角再理解

  • 新生代(Eden + 两个 Survivor):对象朝生夕灭、存活率低,用复制算法;默认 Eden:Survivor = 8:1,一次 Minor GC 后存活对象进 Survivor,年龄达标(默认 15)晋升老年代。
  • 老年代:对象存活率高,用标记-清除或标记-整理,避免复制大量存活对象。
  • 触发时机:Eden 满触发 Minor GC(只回收新生代);老年代/元空间不足或显式 System.gc() 会触发 Full GC(整堆清理,停顿更久)。
  • 为何不统一算法:对象存活率随代龄变化极大,单一算法必然顾此失彼,分代正是按“多数会死 / 多数活着”分别选型。
  • 空间担保:Survivor 放不下时,多出的存活对象会通过分配担保直接进入老年代,避免数据丢失。
  • 收集器视角:Serial、Parallel、CMS、G1 只是把这些算法按代际组合封装,暴露给用户的是参数而非算法本身。

一句话记忆:标记-清除=只清不整(碎片多);复制=搬活对象(耗空间);标记-整理=挪活对象补坑(慢在搬移)。

public class PromotionDemo {
    public static void main(String[] args) {
        // 大对象可能直接进入老年代(-XX:PretenureSizeThreshold 控制)
        byte[] big = new byte[8 * 1024 * 1024];
        big = null; // 失去引用,等待老年代 GC 回收
        // 可用 -XX:+PrintGCDetails 观察各区域回收与晋升日志
        // 组合调优:-Xmn256m -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15
    }
}

实战提醒:频繁 Minor GC 属正常,真正要盯的是 Full GC 频率与停顿;对象频繁晋升老年代,多半是 Survivor 过小或大对象过多。

常见追问 / 记忆点

  • 记忆点:新生代=复制,老年代=标记-清除/标记-整理;本质是“时间、空间、碎片”三角权衡。
  • 追问:为什么新生代敢用复制——因为每次 GC 存活对象极少,复制成本低。
  • 追问:复制算法在存活率高时劣势明显,所以它只适合低存活率的新生代,老年代绝不使用。
  • 追问:算法本身不能直接选,能选的是收集器(如 G1、CMS),收集器内部自动组合上述算法。
笔记加载中…