Skip to content

垃圾回收算法介绍

1. 先明确:GC 算法要解决什么问题

垃圾回收算法的目标不是“把对象删掉”这么简单,而是在以下约束下做权衡:

  • 停顿时间(Pause Time):一次 GC 让业务停多久(STW)。
  • 吞吐量(Throughput):业务时间 / 总时间,吞吐优先的收集器会接受更长停顿。
  • 内存占用(Footprint):为了回收需要预留多少额外空间(复制算法需要 To-space)。
  • 碎片与整理成本:是否会产生碎片、整理(Compaction)成本如何。

面试回答“有哪些 GC 算法”时,最好把每种算法的取舍讲清楚。

2. 标记-清除(Mark-Sweep)

2.1 基本流程

  1. 标记(Mark):从 GC Roots 出发,标记所有可达对象。
  2. 清除(Sweep):遍历堆,回收未标记对象占用的内存块。

2.2 优缺点

  • 优点:实现相对简单,不要求把活对象搬来搬去。
  • 缺点:容易产生内存碎片,导致大对象分配失败或频繁触发 GC。

3. 复制(Copying / Semi-space)

3.1 基本流程(典型:from/to)

  1. 把存活对象从 From 区复制到 To 区。
  2. 复制完成后,清空 From 区,交换 From/To 角色。

3.2 适用场景与代价

  • 适合存活率低的场景:例如新生代对象“朝生夕死”。
  • 代价是需要预留 To-space,空间换时间

新生代常见 Eden + Survivor 的设计,本质上就是复制算法的工程化落地:Minor GC 时把存活对象从 Eden/Survivor 复制到另一个 Survivor,减少整理成本。

4. 标记-整理(Mark-Compact)

4.1 基本流程

  1. 标记可达对象。
  2. 将可达对象向一端移动(整理),让内存变成连续空间。
  3. 清理边界外的空间。

4.2 适用场景

老年代对象存活率高,复制算法成本太大,通常会使用标记-整理或其变体来解决碎片问题。

5. 分代收集(Generational GC)

分代不是一种“回收算法”,而是一种组织堆与选择算法的策略,核心依据是“分代假说”:

  • 大部分对象很快就会变成垃圾(新生代)。
  • 活得越久的对象越可能继续存活(老年代)。

因此 JVM 通常把堆分成新生代/老年代(以及元空间等),并对不同区域应用更合适的算法组合:

  • 新生代:偏向复制,追求快速回收、低成本。
  • 老年代:偏向标记-清除/标记-整理,处理高存活率对象与碎片。

6. 并发与增量

并发收集器为了减少停顿,会让“标记/清理/整理”的一部分与应用线程并发执行。此时对象引用关系会变化,需要用写屏障(Write Barrier)配合 Remembered Set 等结构,保证标记结果正确,避免漏标或多标。