谈谈垃圾回收器的演进-从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 之后,回收器逐步向低延迟、灵活性方向发展。

相关推荐
红尘散仙6 小时前
我把终端小说阅读器接上了 AI Agent:TRNovel 现在能用 skill 生成书源了
人工智能·后端·rust
卷毛的技术笔记7 小时前
告别硬编码!Spring AI Alibaba 实现 AI Agent 智能工具调用(Tool Calling)
java·人工智能·后端·python·spring·ai编程
会编程的土豆7 小时前
Go 语言反射(Reflection)详解
开发语言·后端·golang
喵个咪8 小时前
GoWind Toolkit Go后端代码生成 完整全流程实战
后端·go·orm
basketball6168 小时前
Go 语言从入门到进阶:4. 数组和MAP使用方法总结
开发语言·后端·golang
qq_2518364578 小时前
SpringBoot+Vue 共享电池柜管理系统 完整实现 前后端分离项目实战 完整代码
vue.js·spring boot·后端
zhangxingchao8 小时前
AI 大模型核心六:量化、Workflow 与 Agent、多轮 RAG
前端·人工智能·后端
IT_陈寒10 小时前
Vite打包时遇到的坑,原来问题出在这里
前端·人工智能·后端
ayqy贾杰11 小时前
基层管理的三板斧,在AI时代行不通了
前端·后端·团队管理
Apifox11 小时前
Apifox 5 月更新|Postman 导入优化、Runner 支持非 root 运行、请求代码自动带鉴权
前端·后端·安全