关于java:盘点Java中的那些常用的Garbage-Collector

39次阅读

共计 4181 个字符,预计需要花费 11 分钟才能阅读完成。

GC 总览

Java 是一门面向对象的语言。在应用 Java 的过程中,会创立大量的对象在内存中。而这些对象,须要在用完之后“回收”掉,开释内存资源。这件事咱们称它为垃圾收集(Garbage Collection,简称 GC),而理论执行者就是各种各样的“垃圾收集器”(Garbage Collector,以下也简称 GC)。

为什么会有各种各样的 GC?因为时代在倒退,以前的 GC 可能不能满足当初的需要,所以就会有源源不断的 GC 推出来。先来看一下都有哪些支流的 GC:

新生代:

Serial:单线程,新生代;

ParNew: 多线程,新生代;

Parallel Scavenge:多线程,新生代,关注吞吐量;

老年代:

Serial Old: 单线程,Serial 的老年代版本;

Parallel Old:多线程,Parallel Scavenge 的老年代版本,关注吞吐量;

CMS:多线程,标记 - 革除算法,关注进展工夫,能够与 Serial 和 ParNew 配合。

其它

G1:同时负责新生代和老年代,是目前一段时间支流的垃圾收集器(JDK 9 到当初 JDK 16 的默认垃圾收集器)。

ZGC:在大堆下也能够管制 STW 工夫极短(几毫秒内),在 JDK 11 为试验阶段,在 JDK 15 转正。Oracle 发动,2017 年奉献给 OpenJDK。

Shenandoah GC:进展工夫极短,在 JDK 12 为试验阶段,在 JDK 15 转正。Red Hat 发动,与 ZGC 和 G1 是竞争关系。

STW: Stop The World,指的是进行用户线程。GC 应该尽量避免 STW 或者缩减 STW 的工夫。

各 JDK 版本默认 GC

上面来看一下从 JDK 7 开始在默认 GC(JDK 7 之前的,我就不考古了,当初大家我的项目上用的也少)。

  • 在 JDK 7,默认是 Parallel Scavenge + Serial Old。
  • 在 JDK 8 及 JDK 7u4 之后的版本,默认是 Parallel Scavenge + Parallel Old。
  • 在 JDK 9 到 JDK 16,默认是 G1

严格来说之前的版本应该是 JDK 1.7, JDK 1.8,这里及下文为了表述不便,我就写成了 JDK 7, JDK 8。

聊聊几种支流的 GC

思考到当初大家用的 Java 版本次要都是 JDK 8 起,所以次要聊 JDK 8 及之后可能会用到的 GC。

Parallel Scavenge + Parallel Old

以下简称为 PS,PO

PS + PO 是 JDK 7u4 之后始终到 JDK 9 之前 server 模式都在用的默认 GC 组合。

PS 用于新生代,是一个并行的多线程收集器,应用了复制算法。它关注吞吐量,能够通过参数设置最大 GC 进展工夫、吞吐量的大小等。

PS 收集器相比于 ParNew 来说,多了一个“自适应调节策略”。当 +UseAdaptiveSizePolicy 这个参数关上之后,就不须要手动指定新生代的大小,Eden 和 Survivor 区的比例,降职老年代对象等细节参数了,虚构机会依据以后零碎的运行状况收集性能监控信息,动静调整这些参数以提供最合适的进展工夫或者最大吞吐量。

PO 用于老年代,同样是关注吞吐量。它是一个多线程的收集器,采纳“标记 - 整顿”算法。PO 是在 JDK 1.6 之后才提供的,目标就是为了与 PS 配合。在此之前,PS 只能与 Serial Old 配合(因为框架不适配的起因,不能与 CMS 配合),老年代 GC 效率低下,所以 PO 应运而生。

ParNew + CMS

ParNew 是一个新生代多线程收集器,应用复制算法。它的呈现比 PS 更早,次要是为了代替 Serial 单线程低下的效率。在新生代复制期间,应用多线程来升高 STW 的工夫。

CMS 是 JDK 5 才推出的一款关注进展工夫的 GC。与 PO 不同的是,CMS 应用的是“标记 - 革除”算法,这可能让它实现更小的进展工夫,但代价是会产生大量的空间碎片,可能会在大对象降职老年代时因为没有间断的内存空间,触发 full gc。

理解到这里,其实我有两个疑难。

1 CMS 为什么不应用“标记 - 整顿”算法?

参考《并发垃圾收集器(CMS)为什么没有采纳标记 - 整顿算法来实现?》这篇文章(原文是在 iteye 的一个探讨,但当初曾经找不到了)的说法。GC 代码和用户代码须要放弃同步,能力保障两者察看到的对象图是统一的。而放弃同步有两种做法,一种是 read barrier,一种是 write barrier。因为读远大于写,所以 CMS 应用的是 write barrier。这就导致 CMS 如果用“标记 - 整顿”算法的话,须要在“整顿”的时候 STW,而如果应用“标记 - 革除”算法的话,在“革除”阶段是不必 STW 的。

2 为什么 CMS 素来没作为默认垃圾收集器?

参考知乎这篇文章《为什么 JDK 8 默认应用 Parallel Scavenge 收集器?》的答复。总结有三个起因:

  • Java 应用服务端的场景为主,服务端更专一吞吐量,所以 JDK 8 默认的是 PS + PO;
  • 应用、调优很简单,有高达 70 多个参数
  • “后浪”太优良,CMS 比 G1 早不了多少,5 开始退出,6 成熟,9 被标记弃用,14 被删除。而 G1 是 7 退出,8 成熟,9 正式成为默认。

然而事实上,有很多互联网大厂抉择了 CMS+ParNew 的组合。前段时间美团出了一篇文章《Java 中 9 种常见的 CMS GC 问题剖析与解决》,阿里也用得比拟多,所以还是能够理解一下。

G1

后面提到了,G1 是一个优良的后浪。从 JDK 9 到以后 JDK 16 始终都是默认的 GC。相较于之前的几款 GC 来说,G1 能够说是有一些颠覆性的设计,比方 Region、Card Table、Remember Set 等。

对于 G1,须要一篇独自的文章来介绍。我之前在集体网站上有一篇文章介绍,《JVM – G1》

文章地址:https://yasinshaw.com/article…

在我的集体网站 https://yasinshaw.com,文章页面搜寻“G1”即可搜到。

这里大抵介绍一下它的特点:

  • 低提早优先,即次要侧重于响应能力;
  • 与 CMS 雷同的中央在于,它们都属于并发收集器,在大部分的收集阶段都不须要挂起应用程序;
  • 更准确的预测 GC 进展工夫,能够依据 -XX:MaxGCPauseMillis 参数指定进展工夫;
  • 膨胀闲暇空间不会造成由长 GC 引起的利用进展工夫;
  • G1 没有 CMS 的碎片化问题(或者说不那么重大)。

ZGC

对于 ZGC,笔者理解得也不多。ZGC 在 JDK 11 推出试验版,在 JDK 15 成为正式版。ZGC 的指标是在尽量不就义吞吐量(官网声称,指标是对程序吞吐量影响小于 15%)的状况下,做到极低的进展工夫,并且进展工夫不会随着对的内存大小变大而升高。

尽管 ZGC 在 JDK 15 曾经转正,但还是在不断完善和迭代。JDK 16 最近公布,在这个版本中 ZGC 有 46 个性能加强 %20and%20fixVersion%20%3D%2016%20and%20type%20%3D%20Enhancement)以及 25 个 bug 修复 %20and%20fixVersion%20%3D%2016%20and%20type%20%3D%20Bug)。曾经能够做到均匀暂停工夫约为 50 微秒(0.05 毫秒),最大暂停工夫约为 500 微秒(0.5 毫秒)。暂停工夫不受堆、流动集和根集大小的影响。这个进展工夫,对业务的影响能够说曾经微不足道了,我等只能大呼 NB。

想要深刻理解 ZGC 原理的同学,能够参考美团技术的这篇文章《新一代垃圾回收器 ZGC 的摸索与实际》(只找到知乎官网号的链接)。

Shenandoah GC

按理说 G1 和 ZGC 曾经很牛逼了,为什么还有其它的 GC 进去?学不动了啊,有木有。。。

Shenandoah GC,咱们称它为 SGC 吧,它是 Red Hat 发动的一款 GC,与 ZGC 是 竞争关系。Shenandoah 更像是 G1 的继承者,有很多相同之处。它跟 ZGC 一样,指标也是低进展工夫,不过实现原理有些不同,ZGC 是基于 colored pointers 来实现,而 Shenandoah GC 是基于 brooks pointers 来实现。

SGC 没有承诺进展工夫小于 10ms,也没有说要就义吞吐量(但理论吞吐量有没有升高就不晓得了)。在官网的性能测试图来看,进展工夫也是极低的。

GC(3) Pause Init Mark 0.771ms
GC(3) Concurrent marking 76480M->77212M(102400M) 633.213ms
GC(3) Pause Final Mark 1.821ms
GC(3) Concurrent cleanup 77224M->66592M(102400M) 3.112ms
GC(3) Concurrent evacuation 66592M->75640M(102400M) 405.312ms
GC(3) Pause Init Update Refs 0.084ms
GC(3) Concurrent update references 75700M->76424M(102400M) 354.341ms
GC(3) Pause Final Update Refs 0.409ms
GC(3) Concurrent cleanup 76244M->56620M(102400M) 12.242ms

它只有在标记和更新援用的时候会暂停,看起来也是只有几毫秒的进展工夫。

总结一下

当初市面上很大一部分 Java 程序都还是基于 Java 8(甚至有些是 Java 7 的),所以 PS + PO,和 ParNew + CMS 都是能够理解一下的,毕竟工作中很有可能会用到。尤其是 CMS,大厂用得很多,面试基本上必问。

ZGC 和 SGC,尽管在 JDK 15 都曾经成为正式版,但理论生产中应用很少,有些潜在问题不肯定能很好地发现,能够缓缓去理解和尝试。

能够预感的是,G1 会在将来很长一段时间内成为最支流的垃圾收集器,所以很有必要好好理解一下 G1,做好常识储备。

求个反对

我是 Yasin,一个保持技术原创的博主,我的微信公众号是:编了个程

都看到这儿了,如果感觉我的文章写得还行,无妨反对一下。

文章会首发到公众号,浏览体验最佳,欢送大家关注。

你的每一个转发、关注、点赞、评论都是对我最大的反对!

还有学习资源、和一线互联网公司内推哦

求个反对

我是 Yasin,一个保持技术原创的博主,我的微信公众号是:编了个程

都看到这儿了,如果感觉我的文章写得还行,无妨反对一下。

文章会首发到公众号,浏览体验最佳,欢送大家关注。

你的每一个转发、关注、点赞、评论都是对我最大的反对!

还有学习资源、和一线互联网公司内推哦

正文完
 0