谈谈垃圾回收器的演进-从SerialGC到G1、ZGC、Shenandoah

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 或老年代。
  • 老年代
    1. 初始标记(STW):标记 GC Roots 可达对象,停顿时间短。
    2. 并发标记:与应用线程并发,标记所有存活对象。
    3. 重新标记(STW):修正并发标记中的遗漏,停顿时间稍长。
    4. 并发清除:清除未标记对象,产生碎片。
  • 碎片处理:CMS 不主动整理碎片。如果碎片导致分配失败,会触发 Full GC(单线程标记-整理),停顿时间显著增加。

总结

垃圾回收器的设计和算法选择紧密相关,分代回收是 G1 之前的主流范式。标记-清除的碎片问题通过整理或区域管理解决,而 G1 之后,回收器逐步向低延迟、灵活性方向发展。

相关推荐
Victor3562 分钟前
MongoDB(94)什么是MongoDB Atlas?
后端
苏三说技术12 分钟前
为什么越来越多的大厂抛弃MCP,转向CLI?
后端
Rust研习社22 分钟前
Rust 写时克隆智能指针 Cow
后端·rust·编程语言
董董灿是个攻城狮24 分钟前
库克不再担任苹果 CEO,附全员信
后端
伞伞悦读27 分钟前
Docker 安装 Redis 教程(重点避坑版)
后端
伞伞悦读33 分钟前
Docker 从 C 盘迁移到 D 盘使用教程(Windows + WSL2 + Docker Desktop)
后端
武子康34 分钟前
大数据-273 Spark MLib-决策树分类算法详解:ID3、C4.5、CART 与剪枝原理
大数据·后端·spark
听风者就是我36 分钟前
Harness Engineering:AI Agent 时代的工程化实践
后端
用户05101225729636 分钟前
FFmpeg常用命令行命令
后端
用户9623779544837 分钟前
原理分析 | Agent —— Tomcat 内存马
后端