垃圾回收(Garbage Collection, GC)是自动内存管理的核心技术,用于回收程序中不再使用的内存。以下是常见的垃圾回收算法及其核心原理和应用场景:
1. 基础算法
(1) 标记-清除(Mark-Sweep)
- 原理:
- 标记阶段:从根对象(如全局变量、栈中的引用)出发,标记所有可达对象。
- 清除阶段:遍历堆内存,回收未被标记的对象。
- 优点:实现简单,无需移动对象。
- 缺点:
- 内存碎片化:回收后产生不连续的内存块,可能导致后续分配失败。
- 两次遍历:效率较低。
- 应用:老年代回收(如CMS的初始阶段)。
(2) 复制(Copying)
- 原理:
- 将内存分为两个区域(From 和 To),存活对象从 From 复制到 To 区域。
- 复制完成后,清空 From 区域,交换 From 和 To 的角色。
- 优点:
- 无碎片:存活对象连续排列。
- 高效:仅遍历存活对象。
- 缺点:
- 内存浪费:需要双倍空间。
- 不适合长生命周期对象:频繁复制开销大。
- 应用:新生代回收(如Java的Serial、ParNew收集器)。
(3) 标记-整理(Mark-Compact)
- 原理:
- 标记阶段:同标记-清除。
- 整理阶段:将所有存活对象移动到内存的一端,清理剩余空间。
- 优点:解决碎片化问题。
- 缺点:
- 移动对象成本高:需要更新所有对象引用。
- 两次遍历:标记和整理均需时间。
- 应用:老年代回收(如Serial Old收集器)。
2. 分代收集(Generational Collection)
- 核心思想:基于“弱分代假说”(大多数对象很快死亡),将堆分为新生代和老年代。
- 新生代:存放新对象,使用复制算法(如Eden + Survivor区)。
- 老年代:存放长期存活对象,使用标记-清除或标记-整理。
- 分代优势:
- 针对不同区域选择最佳算法。
- 减少全局垃圾回收的频率。
- 触发条件:
- Minor GC:回收新生代。
- Major GC/Full GC:回收整个堆(包括老年代和元空间)。
3. 引用计数(Reference Counting)
- 原理:
- 每个对象维护一个计数器,记录指向它的引用数量。
- 当计数器归零时,立即回收对象。
- 优点:
- 实时性:无需等待GC周期。
- 简单:适合小型系统。
- 缺点:
- 循环引用:无法回收循环引用的对象(如A→B→A)。
- 性能开销:每次更新引用需修改计数器。
- 应用:Python、Objective-C(需手动解决循环引用)。
4. 增量收集(Incremental GC)
- 原理:将GC过程分为多个小阶段,与用户线程交替执行。
- 优点:减少单次GC的停顿时间(STW)。
- 缺点:整体吞吐量可能下降。
- 应用:实时系统(如CMS的并发标记阶段)。
5. 分区收集(Region-Based GC)
- 原理:将堆划分为多个独立区域(Region),优先回收垃圾较多的区域。
- 优点:
- 灵活控制停顿时间。
- 避免全堆扫描。
- 应用:G1(Garbage-First)收集器(Java 9+默认)。
6. 三色标记法(Tri-color Marking)
- 原理:
- 白色:未标记对象(待回收)。
- 灰色:已标记但子对象未处理。
- 黑色:已标记且子对象处理完成。
- 作用:解决并发标记过程中对象状态变化的难题。
- 应用:CMS、G1等并发收集器。
7. 现代GC算法的发展
- ZGC(Z Garbage Collector):
- 目标:亚毫秒级停顿(<10ms)。
- 核心技术:染色指针(Colored Pointers)、并发整理。
- Shenandoah:
- 特点:并发整理,减少停顿时间。
- 适用:大内存堆(如数十GB)。
GC算法的选择依据
场景 | 推荐算法 | 原因 |
---|---|---|
短生命周期对象 | 分代 + 复制算法 | 高效处理快速死亡对象 |
低延迟需求 | 增量/并发收集(如ZGC) | 最小化停顿时间 |
大内存堆 | 分区收集(如G1) | 局部回收,避免全堆扫描 |
简单嵌入式系统 | 引用计数 | 实现简单,实时回收 |
总结
垃圾回收算法的核心目标是在内存利用率、吞吐量和停顿时间之间找到平衡。现代JVM(如HotSpot)通常结合多种算法(如分代 + 复制 + 并发标记),而新兴算法(如ZGC、Shenandoah)则通过并发和分区技术进一步优化性能。理解这些算法有助于调优程序的内存管理和性能!