垃圾回收算法介绍
1. 先明确:GC 算法要解决什么问题
垃圾回收算法的目标不是“把对象删掉”这么简单,而是在以下约束下做权衡:
- 停顿时间(Pause Time):一次 GC 让业务停多久(STW)。
- 吞吐量(Throughput):业务时间 / 总时间,吞吐优先的收集器会接受更长停顿。
- 内存占用(Footprint):为了回收需要预留多少额外空间(复制算法需要 To-space)。
- 碎片与整理成本:是否会产生碎片、整理(Compaction)成本如何。
面试回答“有哪些 GC 算法”时,最好把每种算法的取舍讲清楚。
2. 标记-清除(Mark-Sweep)
2.1 基本流程
- 标记(Mark):从 GC Roots 出发,标记所有可达对象。
- 清除(Sweep):遍历堆,回收未标记对象占用的内存块。
2.2 优缺点
- 优点:实现相对简单,不要求把活对象搬来搬去。
- 缺点:容易产生内存碎片,导致大对象分配失败或频繁触发 GC。
3. 复制(Copying / Semi-space)
3.1 基本流程(典型:from/to)
- 把存活对象从 From 区复制到 To 区。
- 复制完成后,清空 From 区,交换 From/To 角色。
3.2 适用场景与代价
- 适合存活率低的场景:例如新生代对象“朝生夕死”。
- 代价是需要预留 To-space,空间换时间。
新生代常见 Eden + Survivor 的设计,本质上就是复制算法的工程化落地:Minor GC 时把存活对象从 Eden/Survivor 复制到另一个 Survivor,减少整理成本。
4. 标记-整理(Mark-Compact)
4.1 基本流程
- 标记可达对象。
- 将可达对象向一端移动(整理),让内存变成连续空间。
- 清理边界外的空间。
4.2 适用场景
老年代对象存活率高,复制算法成本太大,通常会使用标记-整理或其变体来解决碎片问题。
5. 分代收集(Generational GC)
分代不是一种“回收算法”,而是一种组织堆与选择算法的策略,核心依据是“分代假说”:
- 大部分对象很快就会变成垃圾(新生代)。
- 活得越久的对象越可能继续存活(老年代)。
因此 JVM 通常把堆分成新生代/老年代(以及元空间等),并对不同区域应用更合适的算法组合:
- 新生代:偏向复制,追求快速回收、低成本。
- 老年代:偏向标记-清除/标记-整理,处理高存活率对象与碎片。
6. 并发与增量
并发收集器为了减少停顿,会让“标记/清理/整理”的一部分与应用线程并发执行。此时对象引用关系会变化,需要用写屏障(Write Barrier)配合 Remembered Set 等结构,保证标记结果正确,避免漏标或多标。