JVM中常见的垃圾收集器

  垃圾收集机制是 Java 的招牌能力,极大地提高了开发效率。如今,垃圾收集几乎成为现代语言的标配,即使经过如此长时间的发展, Java 的垃圾收集机制仍然在不断的演进中,不同大小的设备、不同特征的应用场景,对垃圾收集提出了新的挑战。

垃圾收集器(GC,Garbage Collector)是和具体JVM实现紧密相关的,不同厂商( IBM 、 Oracle ),不同版本的JVM,提供的选择也不同。接下来,我来谈谈最主流的 Oracle JDK。

如下图所示是JDK1.7Update 14之后的HotSpot虚拟机内置的垃圾收集器,我们先从这些谈起,最后在为大家讲解最新的jdk版本中对于垃圾收集器的改进。

上图展示了7种作用于不同分代的收集器,如果两个收集器之间存在连线,就说明它们可以搭配使用。


1.Serial收集器

  Serial收集器是最基本、发展历史最悠久的收集器。是单线程的收集器,采用的是复制算法进行年轻代的回收。它在进行垃圾收集时,必须暂停其他所有的工作线程(Stop-The-World),直到它收集完成。其单线程设计也意味着精简的 GC 实现,无需维护复杂的数据结构,初始化也简单,所以一直是 Client 模式下 JVM 的默认选项。

2.Serial Old 收集器

  Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用标记整理算法,应用于老年代的回收。可以通过设置JVM参数来使用Serial

-XX:+UseSerialGC

3.ParNew收集器

  ParNew收集器其实就是Serial收集器的多线程版本,应用于年轻代一般配合CMS来一起使用,除了使用多线程进行垃圾收集之外,其余行为包括Serial收集器可用的所有控制参数、收集算法、Stop The Worl、对象分配规则、回收策略等都与Serial 收集器完全一样。

  ParNew收集器是许多运行在Server模式下的虚拟机中首选新生代收集器(但不是默认),其中有一个与性能无关但很重要的原因是,除Serial收集器之外,目前只有ParNew它能与CMS收集器配合工作。

开启选项是:

-XX:+UseConcMarkSweepGC -XX:+UseParNewGC

4.Parallel Scavenge(并行回收)收集器

  在早期JDK 8等版本中,它是server模式JVM的默认GC选择,也被称作是吞吐量优先的GC。它的算法和Serial GC比较相似,尽管实现要复杂的多,其特点是新生代和老年代 GC 都是并行进行的,在常见的服务器环境中更加高效。
  该收集器的目标是达到一个可控制的吞吐量(Throughput)。所谓吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即 吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)

开启选项是:

-XX:+UseParallelGC

 Parallel GC 引入了开发者友好的配置项,我们可以直接设置暂停时间或吞吐量等目标, JVM 会自动进行适应性调整,例如下面参数:

-XX:MaxGCPauseMillis=value  //大垃圾收集停顿时间
-XX:GCTimeRatio=N     // GC时间和用户时间比例 = 1 / (N+1) --->  直接设置吞吐量大小

5.Parallel Old 收集器
  Parallel Old 是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。这个收集器在1.6中才开始提供。

6.CMS收集器(Concurrent Mark Sweep)

  基于标记-清除(Mark-Sweep)算法,设计目标是尽量减少停顿时间,这一点对于Web等反应时间敏感的应用非常重要,一直到今天,仍然有很多系统使用 CMS GC 。它的运作过程相对前面几种收集器来说更复杂一些,整个过程分为4个步骤:

(1)初始标记:独占CPU,标记GCROOT直接引用的对象。

(2)并发标记:于用户线程一起并发执行,通过GCROOT标记所有可达的对象

(3)重新标记:独占CPU,因为上一个阶段并发标记用户线程可能会产生新的对象引用,此阶段对并发阶段产生的垃圾进行纠正。

(4)并发清除:于用户线程一起并发执行,清除垃圾

其中,初始标记、重新标记这两个步骤仍然需要“Stop The World”,即独占CPU。

CMS收集器主要优点:并发收集,低停顿。

CMS三个明显的缺点:

(1)CMS收集器对CPU资源非常敏感。因为他有两个阶段是和用户线程并发执行,如果用户获取CPU的能力比较强,则垃圾收集的效率会很底。CPU个数少于4个时,CMS对于用户程序的影响就可能变得很大,为了应付这种情况,虚拟机提供了一种称为“增量式并发收集器”的CMS收集器变种。所做的事情和单CPU年代PC机操作系统使用抢占式来模拟多任务机制的思想

(2)CMS收集器无法处理浮动垃圾。所谓浮动垃圾是指在最后一个并发清除的过程中,用户线程所产生的新的垃圾对象,这一部分对象只能留到下一次GC时进行清理。

  可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生。在JDK1.5的默认设置下,CMS收集器当老年代使用了68%的空间后就会被激活,这是一个偏保守的设置,如果在应用中老年代增长不是太快,可以适当调高参数-XX:CMSInitiatingOccupancyFraction的值来提高触发百分比,以便降低内存回收次数从而获取更好的性能,在JDK1.6中,CMS收集器的启动阀值已经提升至92%。

(3)CMS是基于“标记-清除”算法实现的收集器,收集结束时会有大量空间碎片产生。空间碎片过多,可能会出现老年代还有很大空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前出发FullGC。

  为了解决这个问题,CMS收集器提供了一个-XX:+UseCMSCompactAtFullCollection开关参数(默认就是开启的),用于在CMS收集器顶不住要进行FullGC时开启内存碎片合并整理过程,内存整理的过程是无法并发的,空间碎片问题没有了,但停顿时间变长了。虚拟机设计者还提供了另外一个参数

-XX:CMSFullGCsBeforeCompaction,这个参数是用于设置执行多少次不压缩的Full GC后,跟着来一次带压缩的(默认值为0,标识每次进入Full GC时都进行碎片整理)

开启选项:

-XX:+UseConcMarkSweepGC

7. G1收集器

  G1 GC这是一种兼顾吞吐量和停顿时间的GC实现,他可以应用于年轻代和老年代,是Oracle JDK 9以后的默认GC选项。G1可以直观的设定停顿时间的目标,相比于CMS ,G1未必能做到CMS在最好情况下的延时停顿,但是最差情况要好很多。

  G1 GC 仍然存在着年代的概念,但是其内存结构并不是简单的条带式划分,而是类似棋盘的一个个 region,新生代和老年代不再是物理隔离的了,它们都是一部分Region的集合 。 Region 之间是复制算法,但整体上实际可看作是标记 - 整理( Mark-Compact )算法,可以有效地避免内存碎片,尤其是当 Java 堆非常大的时候, G1 的优势更加明显。

  G1收集器之所以能建立可预测的停顿时间模型,是因为它可以有计划地避免在Java堆中进行全区域的垃圾收集。G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获取的空间大小以及回收所需要的时间的经验值),在后台维护一个优先列表,每次根据设置的收集时间,优先回收价值最大的Region(这也就是Garbage-First名称的又来)。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了G1收集器在有限的时间内可以获取尽量可能高的回收效率

G1 吞吐量和停顿表现都非常不错,并且仍然在不断地完善,与此同时 CMS 由于其天然的不利条件已经在 JDK 9 中被标记为废弃( deprecated ),所以 G1 GC 值得深入掌握。以上的是基本的介绍,如果想深入了解G1的内部原理,推荐查看这篇博客


 

GC的新发展

  GC 仍然处于飞速发展之中,目前的默认选项 G1 GC 在不断的进行改进,很多我们原来认为的缺点,例如串行的 Full GC 、 Card Table 扫描的低效等,都已经被大幅改进,例如:JDK 10 以后, Full GC 已经是并行运行,在很多场景下,其表现还略优于 Parallel GC 的并行 Full GC 实现。
  即使是 Serial GC ,虽然比较古老,但是简单的设计和实现未必就是过时的,它本身的开销,不管是 GC 相关数据结构的开销,还是线程的开销,都是非常小的,所以随着云计算的兴起,在 Serverless 等新的应用场景下, Serial GC 找到了新的舞台。
  比较不幸的是 CMS GC ,因为其算法的理论缺陷等原因,虽然现在还有非常大的用户群体,但是已经被标记为废弃,如果没有组织主动承担 CMS 的维护,很有可能会在未来版本移除。

  随着JVM的不断发展在 JDK 11 ,你会发现,JDK又增加了两种全新的 GC 方式,分别是:Epsilon GC和ZGC

Epsilon GC(爱普色line):简单说就是个不做垃圾收集的GC,似乎有点奇怪,有的情况下,例如在进行性能测试的时候,可能需要明确判断GC本身产生了多大的开销,这就是其典型应用场景。

ZGC:这是Oracle开源出来的一个超级GC实现,具备令人惊讶的扩展能力,比如支持T bytes级别的堆大小,并且保证绝大部分情况下,延迟都不会超过10 ms。虽然目前还处于实验阶段,仅支持 Linux 64 位的平台,但其已经表现出的能力和潜力都非常令人期待。

原文地址:https://www.cnblogs.com/sunweiye/p/10864057.html