JVM垃圾收集器:从Serial到G1的演进之路
JVM垃圾收集器:从Serial到G1的演进之路
引言
垃圾收集一直是Java技术生态中最迷人也最复杂的领域之一。对于许多开发者而言,JVM的垃圾收集机制就像一个黑盒:对象被创建,使用,然后神秘地消失。然而,在生产环境的高并发、低延迟场景下,不理解这个黑盒的运作机制,往往会导致严重的性能瓶颈,甚至系统崩溃。
从最早的Serial收集器到如今成为主流的G1(Garbage-First)收集器,JVM的垃圾收集技术经历了一场漫长的演进。这场演进的核心驱动力,始终是如何在有限的内存和计算资源下,平衡吞吐量与延迟。本文将深入剖析这一演进过程中的核心原理、算法实现,并结合实战代码,帮助你构建完整的GC知识体系。
核心概念:分代与STW
在深入具体收集器之前,我们需要夯实两个基石:分代思想和Stop-The-World(STW)。
分代假说
绝大多数收集器都基于“分代收集”理论。这个理论建立在两个强假说之上:
1. 弱分代假说:绝大多数对象都是朝生夕灭的。
2. 强分代假说:熬过越多次垃圾收集的对象,越难以消亡。
基于此,JVM堆内存被划分为新生代和老年代。新生代存放短命对象,老年代存放长命对象。这种划分允许针对不同区域使用不同的回收算法,从而大幅提高回收效率。
Stop-The-World (STW)
在垃圾收集过程中,JVM需要暂停所有应用线程,以确保对象引用关系在标记过程中不再发生变化。这种暂停被称为STW。STW时间的长短,直接决定了系统的响应延迟。从Serial到G1的演进,本质上就是一部不断缩短STW时间、让垃圾收集与应用线程并发执行的奋斗史。
技术原理:垃圾收集器的四代演进
1. Serial收集器:单线程的基石
Serial是最古老、最稳定的收集器。它采用单线程进行垃圾回收,工作时必须暂停所有工作线程。
- 算法:新生代采用“复制”算法,老年代采用“标记-整理”算法。
- 特点:简单高效,没有线程交互开销。
- 适用场景:单核CPU环境、客户端应用(Client模式)、小内存应用(如<100MB)。
- 演进意义:它是所有后续收集器的原型,奠定了分代回收的基础框架。
2. Parallel Scavenge / Parallel Old:吞吐量优先
随着多核CPU的普及,单线程的Serial成为了瓶颈。Parallel收集器应运而生,它通过多线程并行执行垃圾回收,充分利用多核计算能力。
- 算法:新生代复制算法,老年代标记-整理算法。
- 核心目标:可控的吞吐量。吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)。
- 关键参数:
-XX:MaxGCPauseMillis(最大垃圾收集停顿时间)和-XX:GCTimeRatio(吞吐量大小)。 - 适用场景:后台运算、科学计算、报表生成等不需要用户实时交互的场景。
3. CMS (Concurrent Mark Sweep):低延迟的先驱
CMS是第一款真正意义上关注低延迟的收集器,它实现了垃圾收集线程与用户线程的并发执行。
- 算法:基于“标记-清除”算法。
- 工作流程:
- 初始标记(STW):仅标记GC Roots直接关联的对象,速度很快。
- 并发标记:从GC Roots遍历整个对象图,用户线程同时运行。
- 重新标记(STW):修正并发标记期间用户线程导致引用变化的部分。
- 并发清除:清除标记的垃圾对象,用户线程同时运行。
- 致命缺陷:
- 内存碎片:基于“标记-清除”算法,无法整理空间,导致大对象分配困难,不得不触发Full GC。
- CPU敏感:并发阶段占用CPU资源,降低吞吐量。
- 浮动垃圾:并发清除时用户线程还在产生新垃圾,本次GC无法清理。
4. G1 (Garbage-First):区域化与可预测停顿
G1收集器是JVM垃圾收集技术的里程碑。它摒弃了物理上的分代隔离,将堆内存划分为多个大小相等的独立区域。
-
核心创新:Region机制
G1将堆划分为多个Region,每个Region可以是Eden、Survivor或Old。这种设计使得G1可以只回收部分Region(即Garbage-First的含义),而不必每次都回收整个堆。 -
算法:整体看是“标记-整理”,局部(Region之间)看是“复制”算法。这意味着G1天然解决了CMS的碎片问题。
-
可预测停顿模型:
G1通过维护每个Region的“垃圾价值”(回收能释放的空间 vs 耗时),在用户设定的停顿时间目标内(MaxGCPauseMillis),优先回收价值最大的Region。 -
记忆集与写屏障:
为了解决跨Region引用问题,

浙公网安备 33010602011771号