JVM 不仅是大厂面试的一个高频问题,也是 Java程序员跨入高职级必须掌握的知识点,垃圾回收器作为 JVM中核心的一环,了解它的原理,可以帮助我们更好地调优和故障排除,因此,今天我们就来聊聊 JVM中 9款常见的垃圾回收器。
背景
因为 Java虚拟机的类型比较多,如果没有特殊说明,本文特指 HotSpot虚拟机,在分享回收器之前,我们首先对 HotSpot 虚拟机背景做个简单的介绍。
HotSpot VM,最初是由 “Longview Technologies” 这家小公司设计,并且一开始也不是为 Java语言研发。
1997年,Sun公司收购了这家公司,从而也就得到了 HotSpot虚拟机,在 Sun公司的一番优化下,HotSpot 虚拟机就成了 Sun/OracleJDK 和 OpenJDK共同的默认虚拟机。
2010年,Oracle 收购 Sun公司,HotSpot 虚拟机也就顺理成章成为了 Oracle旗下产品。
Sun/OracleJDK 和 OpenJDK 都是 Oracle 旗下产品,Sun/OracleJDK 是商用版,OpenJDK 是免费版,两款虚拟机的内核是一样,只是功能略有差异。
关于使用的是 Sun/OracleJDK 还是 OpenJDK ,可以通过 java -version 指令查看。
Sun/OracleJDK:
OpenJDK:
1.Serial
Serial 收集器,见名知意,它是一个单线程的收集器,而且在进行垃圾回收时还必须暂停其它的工作线程,直到它收集结束(Stop The World)。
在 JDK 1.3.1 之前,它是 HotSpot虚拟机年轻代收集器的唯一选择。
Serial(年轻代) 和 Serial Old(老年代) 组合模式下,收集器大致的工作流程如下图:
尽管 Serial 收集器是单线程回收,并且会暂停其它的工作线程,看起来性能很差,但是,它依然是 HotSpot 虚拟机运行在客户端模式下的默认新生代收集器,因为相对于其它收集器的单线程,Serial 收集器消耗的内存最低,加上没有多线程交互的开销,反而使得它简单高效。
在启动 Java进程时,可以通过设置 -XX:+UseSerialGC -XX:+UseSerialOldGC 参数,使用上述回收器组合。
2.ParNew
ParNew 收集器是 Serial 收集器的多线程并行版本,除了使用多线程进行垃圾回收之外,其它的行为和 Serial 收集器都是相同的。主要应用在 HotSpot虚拟机运行在服务端模式下的场景。
ParNew(年轻代) 和 Serial Old(老年代) 组合模式下,收集器大致的工作流程如下图:
在启动 Java进程时,可以通过设置 -XX:+UseParNewGC -XX:+UseSerialOldGC 参数,使用上述回收器组合。
3.Parallel Scavenge
Parallel Scavenge 收集器也是一款用于年轻代的回收器,它和 ParNew 收集器一样,采用多线程并发回收,但是,Parallel Scavenge可以通过 -XX:MaxGCPauseMillis 参数设置 GC的最大停顿时间,这样就可以达到一个吞吐量(Throughput)可控的目标,从而优于 ParNew回收器。
Parallel Scavenge(年轻代) 和 Serial Old(老年代) 组合模式下,收集器大致的工作流程如下图:
在启动 Java进程时,可以通过设置 -XX:+UseParallelGC -XX:+UseSerailOldGC 参数,使用上述回收器组合。
但是,这种组合看起来很尴尬,年轻代使用的多线程并发收集,而老年代却使用单线程进行回收,怎么看起来老年代的回收都是“拖累”,因此,用于老年代的Parallel Old 并发收集器就诞生了。
Parallel Scavenge(年轻代) 和 Parallel Old(老年代) 组合模式下,收集器大致的工作流程如下图:
在启动 Java进程时,可以通过设置 -XX:+UseParallelGC -XX:+UseParallelOldGC 参数,使用上述回收器组合。
4.Serial Old
Serial Old 收集器是 Serial 的老年代版本,它也是一个单线程收集器,使用‘标记-整理’算法,和 Serial 收集器一样也是用于 HotSpot客户端模式。
Serial(年轻代) 和 Serial Old(老年代) 组合模式下,收集器大致的工作流程如下图:
在启动 Java进程时,可以通过设置 -XX:+UseSerialGC -XX:+UseSerialOldGC 参数,使用上述回收器组合。
5.Parallel Old
Parallel Old 收集器是从 JDK 6 开始提供支持的,它是 Parallel Scavenge 收集器的老年代版本,支持多线程并发收集,采用‘标记-整理’算法。Parallel Old 收集器的出现,真正意义上实现了“吞吐量优先”的目标。
Parallel Scavenge(年轻代) 和 Parallel Old(老年代) 组合模式下,收集器大致的工作流程如下图:
在启动 Java进程时,可以通过设置 -XX:+UseParallelGC -XX:+UseParallelOldGC 参数,使用上述回收器组合。
6.CMS
CMS 收集器,从 JDK5发布之后正式诞生,可以毫不夸张地说:CMS是一个跨时代的收集器,曾几何时,它是各互联网大厂面试中垃圾回收器的必问知识点。
CMS 是 Comcurrent Mark Sweep 的简称,用于老年代的垃圾回收。CMS的收集过程包含 5个步骤:
- Initial Mark(初始标记) Stop The World
- Concurrent Marking(并发标记)
- Remark(重复标记) Stop The World
- Concurrent Sweep(并发清除)
- Resetting(重置)
CMS 收集器大致的工作流程如下图:
尽管 CMS回收器实现了回收线程与应用线程能同时并发工作的目标,但它也有致命的问题:无法处理“浮动垃圾”,有可能出现 Concurrent Mode Failure 失败,导致Full GC。因此,Oracle官方目前已经将 CMS 申明为 “deprecated”,不推荐使用。这也宣告了 CMS收集器的历史使命已结束。
在启动 Java进程时,可以通过设置-XX:+UseConcMarkSweepGC 参数,显示使用 CMS回收器。
7.G1
G1 回收器是 Garbage First 的简称, 它是一款面向服务器的垃圾回收器,用于大内存的多处理器计算机,目标是实现低延时垃圾回收。
从 Oracle JDK 7 Update 4 及更高版本已完全支持 G1,并且 JDK9 开始,G1 已经成为了默认的垃圾收集器。
应该说,G1是垃圾回收器历史上的一个里程碑,开启了基于 Region回收的时代,和以往的垃圾回收器不一样,G1尽管依然保留了年轻代和老年代的概念,但是各代存储地址是不连续的,每一代包含了 n个大小相同且不连续的 Region,G1 的堆内存分配如下图:
G1提供了两种 GC模式:Young GC和 Mixed GC。
G1的收集过程包含 4个步骤:
(1) Initial Marking(初始标记):标记了从 GC Root开始直接可达的对象
(2) Concurrent Marking(并发标记):在整个堆上查找活动对象,标记全部可达对象。这个阶段可能会被年轻代垃圾回收中断。
(3) Remark(重新标记):完成对堆中活动对象的标记。使用一种称为“快照在开始时”(Snapshot-at-the-Beginning,SATB)的算法,其速度比 CMS收集器中使用的算法要快得多。
(4) Cleanup(清除垃圾):该过程完成 3个事情
- 对活动对象和完全释放的区域进行记账。(Stop The World)
- 清理已记住的集合。(Stop The World)
- 重置空的区域并将其返回到空闲列表。(并发执行)
G1 收集器大致的工作流程如下图:
在启动 Java进程时,可以通过设置 -XX:+UseG1GC 参数,显示使用 G1回收器。
8.Shenandoah
Shenandoah 也是一款 HotSpot 虚拟机回收器,首次出现在Open JDK12中,最初是由 RedHat公司开发,2014年贡献给 OpenJDK,或许因为它不是 Oracle公司自己开发的,所以,Shenandoah 目前只存在 OpenJDK 而不存在 OracleJDK商业版中。Shenandoah主要使用连接矩阵和转发指针的技术,连接矩阵替代 G1中的卡表。
Shenandoah工作流程分为 9个步骤:
- Initial Marking(初始标记):和G1 一样,标记了从 GC Root开始直接可达的对象,Stop The World
- Concurrent Marking(并发标记):和G1 一样,在整个堆上查找活动对象,标记全部可达对象。
- Final Marking(最终标记):和G1 一样,
- Concurrent Cleanup(并发清理):清理无存活对象的 Region
- Concurrent Evacuation(并发回收):把存活的对象复制到空的 Region中,
- Inital Update Reference(初始引用更新):修正并发回收阶段被复制对象的引用地址
- Concurrent Update Reference(并发引用更新):引用更新操作
- Final Update Reference(最终引用更新):修正存在于 GCRoots中的引用
- Concurrent Cleanup(并发清理):回收空的 Region
2. Concurrent Marking(并发标记):和G1 一样,在整个堆上查找活动对象,标记全部可达对象。
Shenandoah 收集器大致的工作流程如下图(图片来自 OpenJDK官方):
在启动 Java进程时,可以通过设置XX:+UseShenandoahGC参数,显示使用 Shenandoah回收器。
注意,如果使用的是Sun/OracleJDK,将无法使用该回收器。
9.ZGC
ZGC 是 Oracle官方研发并从 JDK11中引入,它是一款采用染色指针和读屏障技术的回收器,ZGC 和 G1一样,堆空间被划分成多个 Region,不同的是,ZGC的 Region 被官方称为Page,它可以动态创建和销毁,容量也可以动态调整。
ZGC的 Region分为三种:
- 小型 Region:容量固定为 2MB,用于存放 < 256KB的对象;
- 中型 Region:容量固定为 32MB,用于存放 >= 256KB且 < 4MB的对象;
- 大型 Region:容量为 2^n MB,存放 >= 4MB 的对象,而且每个大型Region 中只存放一个大对象。由于大对象移动代价过大,所以该对象不会被重分配。
ZGC 工作流程分为 4个步骤:
- Concurrent Mark(并发标记):和G1 一样,标记了从 GC Root开始直接可达的对象
- Concurrent Prepare for Relocate(并发预备重分配)
- Concurrent for Relocate(并发重分配)
- Concurrent Remap(并发重映射)
ZGC 收集器大致的工作流程如下图:
ZGC垃圾回收过程几乎全部是并发,实际 Stop The World(STW)停顿时间极短,不到10ms。这得益于其采用的着色指针和读屏障技术。
在启动 Java进程时,可以通过设置XX:+UseZGC参数,显示使用 ZGC回收器。
到此,9款垃圾收集器就介绍完毕,如果你对垃圾回收器很感兴趣,推荐阅读周志明博士的《深入理解Java虚拟机》第三版,书中除了垃圾回收器, JVM其它相关的内容也都有详细地介绍,应该是国内很多 Java程序员学习 JVM的必备书籍。
因为篇幅有限,本文只是简单地分析了 HotSpot虚拟机常见的 9款垃圾回收器,并没有做原理上的分析,我会在接下来的文章中分别对 CMS,G1,ZGC,Shenandoah 4款垃圾收集器做详细的讲解,链接:JVM专栏 。最后用一张图表对 9款回收器做一个对比: