1. 垃圾回收器的演进历史
垃圾回收技术随着编程语言和硬件的发展逐步演进。最早的垃圾回收可以追溯到 1959 年 John McCarthy 为 Lisp 语言设计的简单标记-清除(Mark-Sweep)算法。随着 Java 等现代语言的兴起,垃圾回收器变得更加复杂和高效。以下是 Java 中垃圾回收器的简要演进历史:
- Serial GC(串行垃圾回收器):Java 早期版本的默认回收器,单线程运行,适合单核 CPU 和小内存场景。
- Parallel GC(并行垃圾回收器):Java 5 引入,利用多线程并行回收,目标是提高吞吐量,适合多核 CPU。
- CMS(Concurrent Mark-Sweep):Java 5 引入的并发回收器,目标是降低停顿时间(低延迟),通过与应用线程并发执行部分回收工作实现。
- G1(Garbage First):Java 7 正式引入(Java 9 默认),目标是平衡吞吐量和低延迟,采用区域化(Region-based)内存管理,逐步取代 CMS。
- ZGC 和 Shenandoah:Java 11 和 12 引入的超低延迟回收器,停顿时间缩短到毫秒级,适合大规模、低延迟应用。
演进的驱动力主要是硬件(从单核到多核)、应用需求(吞吐量 vs. 低延迟)和内存管理复杂性的增加。
2. 分代回收:年轻代和老年代
垃圾回收器分为年轻代(Young Generation)和老年代(Old Generation),这是 Java 中分代回收(Generational GC)的核心思想。分代回收基于"弱分代假说":
- 大多数对象很快变得不可达(短生命周期)。
- 老对象很少引用新对象。
基于此,Java 堆被划分为:
- 年轻代:包括 Eden 区和两个 Survivor 区(S0 和 S1),用于存放新创建的对象。年轻代使用"复制算法"(Copying),因为对象存活率低,复制少量存活对象效率高。
- 老年代:存放经过多次 GC 仍存活的对象,通常使用"标记-清除"或"标记-整理"算法,因为存活对象较多。
在 G1 之前,Java 的主流垃圾回收器(如 Serial、Parallel、CMS)都采用分代回收设计。G1 虽然也支持分代,但它引入了"区域"(Region)的概念,不再严格区分连续的年轻代和老年代,而是动态分配区域的角色(Eden、Survivor、Old),更灵活。
3. 垃圾回收算法
垃圾回收算法是垃圾回收器的实现基础,主要有以下几种:
(1) 标记-清除(Mark-Sweep)
- 过程:先标记存活对象,然后清除未标记的对象。
- 优点:实现简单,不移动对象。
- 缺点:产生内存碎片(不连续的空闲内存块)。
- 碎片处理:标记-清除本身不处理碎片。如果碎片过多,可能触发"标记-整理"或 Full GC 来整理内存。CMS 回收器就使用标记-清除,碎片问题通过定期整理或用户手动触发 Full GC 解决。
(2) 标记-复制(Mark-Copy)
- 过程:将内存分为两块(From 和 To),标记存活对象后,将其复制到另一块,清空原区域。
- 优点:无碎片,适合存活对象少的场景。
- 缺点:内存利用率低(一半空间闲置)。
- 应用:年轻代的 Minor GC 常用,比如 Serial 和 Parallel GC 的年轻代实现。
(3) 标记-整理(Mark-Compact)
- 过程:标记存活对象后,将其移动到内存一端,清除剩余部分。
- 优点:无碎片,内存利用率高。
- 缺点:移动对象开销大,停顿时间长。
- 应用:老年代常用,比如 Parallel Old GC 和 CMS 的 Full GC 阶段。
4. 垃圾回收器与算法的对应关系
不同垃圾回收器组合使用上述算法:
- Serial GC:年轻代用标记-复制,老年代用标记-整理。
- Parallel GC:年轻代用标记-复制,老年代用标记-整理(Parallel Old)。
- CMS:年轻代用标记-复制(ParNew),老年代用标记-清除(并发执行),碎片问题依赖 Full GC 解决。
- G1:年轻代用标记-复制,老年代用混合回收(Mixed GC),结合标记-清除和局部整理,碎片通过区域管理缓解。
- ZGC/Shenandoah:使用标记-整理,但通过并发移动对象实现超低停顿。
5. G1 之前都是分代回收吗?
是的,在 G1 之前,Java 的主流垃圾回收器(Serial、Parallel、CMS)都严格基于分代回收设计,分年轻代和老年代。G1 虽然保留了分代思想,但通过区域化管理打破了传统分代回收的固定内存布局。它将堆划分为多个小区域(Region),动态分配 Eden、Survivor 和 Old 角色,回收时优先处理"垃圾最多"的区域(Garbage First),因此更灵活。
6. 更具体的解释:以 CMS 为例
以 CMS(Concurrent Mark-Sweep)为例,详细说明其工作方式:
- 年轻代:使用 ParNew(并行标记-复制),多线程复制存活对象到 Survivor 或老年代。
- 老年代 :
- 初始标记(STW):标记 GC Roots 可达对象,停顿时间短。
- 并发标记:与应用线程并发,标记所有存活对象。
- 重新标记(STW):修正并发标记中的遗漏,停顿时间稍长。
- 并发清除:清除未标记对象,产生碎片。
- 碎片处理:CMS 不主动整理碎片。如果碎片导致分配失败,会触发 Full GC(单线程标记-整理),停顿时间显著增加。
总结
垃圾回收器的设计和算法选择紧密相关,分代回收是 G1 之前的主流范式。标记-清除的碎片问题通过整理或区域管理解决,而 G1 之后,回收器逐步向低延迟、灵活性方向发展。