9款常见的 JVM垃圾回收器

你好,我是猿java

JVM 不仅是大厂面试的一个高频问题,也是 Java程序员跨入高职级必须掌握的知识点,垃圾回收器作为 JVM中核心的一环,了解它的原理,可以帮助我们更好地调优和故障排除,因此,今天我们就来聊聊 JVM中 9款常见的垃圾回收器。

img_2.png

背景

因为 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:

img_2.png

  • OpenJDK:

img_2.png

1. Serial

Serial 收集器,见名知意,它是一个单线程的收集器,而且在进行垃圾回收时还必须暂停其它的工作线程,直到它收集结束(Stop The World)。

在 JDK 1.3.1 之前,它是 HotSpot虚拟机年轻代收集器的唯一选择。

Serial(年轻代) 和 Serial Old(老年代) 组合模式下,收集器大致的工作流程如下图:

img_2.png

尽管 Serial 收集器是单线程回收,并且会暂停其它的工作线程,看起来性能很差,但是,它依然是 HotSpot 虚拟机运行在客户端模式下的默认新生代收集器,因为相对于其它收集器的单线程,Serial 收集器消耗的内存最低,加上没有多线程交互的开销,反而使得它简单高效。

在启动 Java进程时,可以通过设置 -XX:+UseSerialGC -XX:+UseSerialOldGC 参数,使用上述回收器组合。

2. ParNew

ParNew 收集器是 Serial 收集器的多线程并行版本,除了使用多线程进行垃圾回收之外,其它的行为和 Serial 收集器都是相同的。主要应用在 HotSpot虚拟机运行在服务端模式下的场景。

ParNew(年轻代) 和 Serial Old(老年代) 组合模式下,收集器大致的工作流程如下图:

img_2.png

在启动 Java进程时,可以通过设置 -XX:+UseParNewGC -XX:+UseSerialOldGC 参数,使用上述回收器组合。

3. Parallel Scavenge

Parallel Scavenge 收集器也是一款用于年轻代的回收器,它和 ParNew 收集器一样,采用多线程并发回收,但是,Parallel Scavenge可以通过 -XX:MaxGCPauseMillis 参数设置 GC的最大停顿时间,这样就可以达到一个吞吐量(Throughput)可控的目标,从而优于 ParNew回收器。

Parallel Scavenge(年轻代) 和 Serial Old(老年代) 组合模式下,收集器大致的工作流程如下图:

img_2.png

在启动 Java进程时,可以通过设置 -XX:+UseParallelGC -XX:+UseSerailOldGC 参数,使用上述回收器组合。

但是,这种组合看起来很尴尬,年轻代使用的多线程并发收集,而老年代却使用单线程进行回收,怎么看起来老年代的回收都是“拖累”,因此,用于老年代的Parallel Old 并发收集器就诞生了。

Parallel Scavenge(年轻代) 和 Parallel Old(老年代) 组合模式下,收集器大致的工作流程如下图:

img_2.png

在启动 Java进程时,可以通过设置 -XX:+UseParallelGC -XX:+UseParallelOldGC 参数,使用上述回收器组合。

4. Serial Old

Serial Old 收集器是 Serial 的老年代版本,它也是一个单线程收集器,使用‘标记-整理’算法,和 Serial 收集器一样也是用于 HotSpot客户端模式。

Serial(年轻代) 和 Serial Old(老年代) 组合模式下,收集器大致的工作流程如下图:

img_3.png

在启动 Java进程时,可以通过设置 -XX:+UseSerialGC -XX:+UseSerialOldGC 参数,使用上述回收器组合。

5. Parallel Old

Parallel Old 收集器是从 JDK 6 开始提供支持的,它是 Parallel Scavenge 收集器的老年代版本,支持多线程并发收集,采用‘标记-整理’算法。Parallel Old 收集器的出现,真正意义上实现了“吞吐量优先”的目标。

Parallel Scavenge(年轻代) 和 Parallel Old(老年代) 组合模式下,收集器大致的工作流程如下图:

img_2.png

在启动 Java进程时,可以通过设置 -XX:+UseParallelGC -XX:+UseParallelOldGC 参数,使用上述回收器组合。

6. CMS

CMS 收集器,从 JDK5发布之后正式诞生,可以毫不夸张地说:CMS是一个跨时代的收集器,曾几何时,它是各互联网大厂面试中垃圾回收器的必问知识点。

CMS 是 Comcurrent Mark Sweep 的简称,用于老年代的垃圾回收。CMS的收集过程包含 5个步骤:

  1. Initial Mark(初始标记) Stop The World

  2. Concurrent Marking(并发标记)

  3. Remark(重复标记) Stop The World

  4. Concurrent Sweep(并发清除)

  5. Resetting(重置)

CMS 收集器大致的工作流程如下图:

img_2.png

尽管 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 的堆内存分配如下图:

img_2.png

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 收集器大致的工作流程如下图:

img_2.png

在启动 Java进程时,可以通过设置 -XX:+UseG1GC 参数,显示使用 G1回收器。

8. Shenandoah

Shenandoah 也是一款 HotSpot 虚拟机回收器,首次出现在Open JDK12中,最初是由 RedHat公司开发,2014年贡献给 OpenJDK,或许因为它不是 Oracle公司自己开发的,所以,Shenandoah 目前只存在 OpenJDK 而不存在 OracleJDK商业版中。Shenandoah主要使用连接矩阵和转发指针的技术,连接矩阵替代 G1中的卡表。

Shenandoah工作流程分为 9个步骤:

  1. Initial Marking(初始标记):和G1 一样,标记了从 GC Root开始直接可达的对象,Stop The World
  2. Concurrent Marking(并发标记):和G1 一样,在整个堆上查找活动对象,标记全部可达对象。
  3. Final Marking(最终标记):和G1 一样,
  4. Concurrent Cleanup(并发清理):清理无存活对象的 Region
  5. Concurrent Evacuation(并发回收):把存活的对象复制到空的 Region中,
  6. Inital Update Reference(初始引用更新):修正并发回收阶段被复制对象的引用地址
  7. Concurrent Update Reference(并发引用更新):引用更新操作
  8. Final Update Reference(最终引用更新):修正存在于 GCRoots中的引用
  9. Concurrent Cleanup(并发清理):回收空的 Region

Shenandoah 收集器大致的工作流程如下图(图片来自 OpenJDK官方):

img_2.png

在启动 Java进程时,可以通过设置XX:+UseShenandoahGC参数,显示使用 Shenandoah回收器。

注意,如果使用的是Sun/OracleJDK,将无法使用该回收器。

9. ZGC

ZGC 是 Oracle官方研发并从 JDK11中引入,它是一款采用染色指针和读屏障技术的回收器,ZGC 和 G1一样,堆空间被划分成多个 Region,不同的是,ZGC的 Region 被官方称为Page,它可以动态创建和销毁,容量也可以动态调整。

ZGC的 Region分为三种:

  1. 小型 Region:容量固定为 2MB,用于存放 < 256KB的对象;

  2. 中型 Region:容量固定为 32MB,用于存放 >= 256KB且 < 4MB的对象;

  3. 大型 Region:容量为 2^n MB,存放 >= 4MB 的对象,而且每个大型Region 中只存放一个大对象。由于大对象移动代价过大,所以该对象不会被重分配。

ZGC 工作流程分为 4个步骤:

  1. Concurrent Mark(并发标记):和G1 一样,标记了从 GC Root开始直接可达的对象

  2. Concurrent Prepare for Relocate(并发预备重分配)

  3. Concurrent for Relocate(并发重分配)

  4. Concurrent Remap(并发重映射)

ZGC 收集器大致的工作流程如下图:

img_2.png

ZGC垃圾回收过程几乎全部是并发,实际 Stop The World(STW)停顿时间极短,不到10ms。这得益于其采用的着色指针和读屏障技术。

在启动 Java进程时,可以通过设置XX:+UseZGC参数,显示使用 ZGC回收器。

因为篇幅有限,本文只是简单地分析了 HotSpot虚拟机常见的 9款垃圾回收器,并没有做原理上的分析,我会在接下来的文章中分别对 CMS,G1,ZGC,Shenandoah 4款垃圾收集器做详细的讲解,链接:JVM专栏 。最后用一张图表对 9款回收器做一个对比:

img_2.png

参考资料

GC:https://www.oracle.com/java/technologies/javase/hotspot-garbage-collection.html

Shenandoah GC:https://wiki.openjdk.org/display/shenandoah/Main

ZGC:https://wiki.openjdk.org/display/zgc/Main
ZGC:https://wiki.openjdk.org/display/zgc/Main

周志明《深入理解Java虚拟机》第三版

交流学习

最后,把猿哥的座右铭送给你:投资自己才是最大的财富。由于水平有限,如果文章存在缺点和错误,欢迎批评指正。感谢转发给更多的好友,关注我:猿java,为你呈现更多的硬核文章。

drawing