转载

译:谁是 JDK8 中最快的 GC

我们都知道 OpenJDK8 有好几个垃圾回收算法,比如 ParallelGC,CMS,还有 G1,那么哪个才是最快的?如果 GC 算法从 Java8 中默认的 ParallelGC 切换到 G1 会发生什么(JDK9 就是把默认 GC 从 ParallelGC 切到了 G1)?废话不多说,做一个基准测试就知道了,Let’s benchmark it.

基准测试方法

  1. 分别用不同的 JVM 参数运行 6 次同样的代码。这些VM参数为:-XX:+UseSerialGC, -XX:+UseParallelGC, -XX:+UseConcMarkSweepGC, -XX:ParallelCMSThreads=2, -XX:ParallelCMSThreads=4, -XX:+UseG1GC。

  2. 每次运行大概花 55 分钟。

  3. 除了指定 GC 的 JVM 参数,其他的 JVM 参数为:

  • -Xmx2048M -server
  • OpenJDK version: 1.8.0_51
  • Software: Linux version 4.0.4-301.fc22.x86_64
  • Hardware: Intel® Core™ i7-4790 CPU @ 3.60GHz
  1. 每次通过 optaplanner 解决 13 个问题,每个问题大概 5 分钟,并且前 30 秒的 JVM 预热时间不计算在内。

  2. 解决问题时不会发生IO,运行过程中,单个CPU完全饱和,并且会一直创建很多生命周期很短的对象,然后GC负责收集它们。

  3. 基准测试测量每毫秒能被计算的分数,越高表示越好。需要说明的是,计算一个分数可不是一件容易的事情,它涉及很多计算,有兴趣的话,可以去 optaplanner 查看它们的源码。

如果想要在你的本地复制演示这个基准测试,只需要基于源码构建 optaplanner,然后运行 main 类 GeneralOptaPlannerBenchmarkApp 即可。

OptaPlanner: https://www.optaplanner.org 。

基准测试结果

每个垃圾回收器和 Java8 默认的 ParallelGC 的对比如下图所示:

译:谁是 JDK8 中最快的 GC

结果非常清晰,JDK8 默认的 ParallelGC 是最快的,其他垃圾回收器相比默认的 ParallelGC 都会有不同程度的衰减,并且 G1 表现最差,是最慢的。

Java9 应该将 G1 设置为默认吗?

JDK 有一个提案建议将 G1 作为服务端默认的垃圾收集器,详情请戳链接:JEP 248: Make G1 the Default Garbage Collector: http://openjdk.java.net/jeps/248 ,我的第一反应是拒绝这个提案,并且上面的压测结果给了我充足的理由:

  • G1 相比平均水平慢了 17.60%.
  • 对于每一种数据集来说,G1在每个用例上一直是更慢的。
  • 在最大的数据集 (Machine Reassignment B10)中,G1慢了34.07%。

其他方面,也有一些需要注意的地方:

  • G1重点是限制GC停顿时间,而不是吞吐量。对于那些侧重于计算的用例,GC停顿时间长短都不是很重要。
  • 本次基准测试几乎是单线程的,将来,并行或者多线程求解的基准测试.可能会影响结果。
  • G1 更推荐在至少 6G 的堆上使用,而基准测试只使用 2G 的堆。
  • 侧重计算只是 OpenJDK 众多使用场景之一,一些其他的场景如 Web 服务,也许就会值得更改默认 GC。

Java9 更改默认 GC

自 G1 完全支持以来,它一直被吹捧为 CMS 的替代者。但是,社区关心的这个JEP248 却是要用 G1 取代 ParallelGC,而不是 CMS。人们普遍认为,由于业务从 CMS 迁移到 G1,因此有数据对比 CMS 和 G1,但是却没有足够的数据对比 ParallelGC(现在默认的 GC)和 G1(准备默认的 GC)。同时,数据表明,许多业务还在使用默认的 GC,即 ParallelGC。因此,当 G1 变成默认的 GC 后,肯定能观察到一些 GC 行为的变化。

Java9 将 G1 设置为默认 GC 的最主要动机是 G1 能 减少 FullGC 的次数 ,这是 G1 相比 JDK8 默认的 ParallelGC 一个很大的改进。G1 的目标是在 不受到堆大小或存活对象数量限制 的情况下最小化暂停时间。这是通过并发进行大部分 GC 工作,只对部分堆的压缩来实现的,这个 GC 过程被称为 mixed gc 。尽可能避免做 FullGC 是 G1 的主要优点之一。

通常来说,限制 GC 停顿时间比追求大吞吐量更重要。对许多用户来说,切换到 G1 这种低延迟的垃圾收集器相比 JDK8 以前默认的吞吐量优先的垃圾收集器 ParallelGC,应该能提供更好的整体体验。

为什么是 G1

G1 GC 被当做是 CMS 的长期替代品,现在的 CMS 有一个很大的问题,将导致并发模式失败,最终导致收集并压缩整个堆(FullGC)。当然,你也可以调优 CMS,延迟这种单线程压缩整个堆的 FullGC,但是,随着使用 CMS 的 JVM 运行的时间越来越长,最终一定 不能避免 (发生 FullGC),注意措词,是 一定

将来,这种 FullGC 会被优化成多线程并行(JDK10),但是,还是不能避免FullGC(只不过,现在是单线程 FullGC,以后是多线程 FullGC)感兴趣的话,请戳链接 JEP 307: Parallel Full GC for G1: https://openjdk.java.net/jeps/307。

另外一个重要的点是,即使是经验丰富的 GC 工程师,维护好 CMS 也被证明是非常具有挑战性和不确定性的。

还有,CMS,ParallelGC 以及 G1 都是基于不同的 GC 框架来实现的,如此以来,导致维护代价非常大。而 G1 是基于 Region 设计的堆框架,这是未来发展的方向。IBM 的 Balanced GC,Azul 的C4,以及 OpenJDK 的 Shenandoah GC,都是同类的基于 Region 设计实现。

放弃 ParallelGC

ParallelGC 不能做递增式的收集。因此,它为了吞吐量就会牺牲延迟性。 随着负载加大,以及更大的堆,GC 的停顿时间也会增加 。这样的话,可能会影响与延迟相关的系统级协议(SLA)。

G1 能帮助你满足 SLA,而且 G1 的 Mixed GC 停顿时间远比 ParallelGC 的 FullGC 要短的多(当然,G1 也有 FullGC,但是其发生的次数可比 ParallelGC 的 FullGC 次数少很多)。

放弃 CMS

当前情况下,CMS 由于 碎片化问题并发模式失败 问题而很可能无法满足 SLA,而调优后的 G1 却能够满足 SLA。G1 的mixed gc 最糟糕情况的停顿时间,也要比 CMS 遭遇的最糟糕的整个堆压缩停堆时间要更好。还有前面提到的,CMS 堆的碎片化问题,导致单线程的 FullGC 只能推迟, 不可能 完全阻止。

因此,CMS 已经被放弃,并且在 JDK9 中被标记为 deprecated,未来的版本会被移除,参考 JEP 291: Deprecate the Concurrent Mark Sweep (CMS) Garbage Collector,相关连接: http://openjdk.java.net/jeps/291

另外,像谷歌这样的公司,基于 OpenJDK 源码构建和运行它们自己私有的 JDK,其特定的源码根据它们的需求而有所改变。例如,谷歌工程师提到:为了减少碎片化,他们为他们的 CMS 的重新标记阶段,增加了一种增量压缩,使它们的 CMS 更可靠。

(参考链接: http://mail.openjdk.java.net/pipermail/hotspot-dev/2015-July/019534.html ).

注意:增量压缩也有自己的成本,谷歌可能在权衡其特定使用场景的好处后,才会增加增量压缩。

结论

没有最好的垃圾回收器,只有更适合业务的垃圾回收器。如果对 GC 的停顿时间很敏感,那么请使用 G1,比如 WEB 服务器;如果对吞吐量有很大的要求,建议使用 ParallelGC,比如 OptaPlanner 这种测试用例。

原文  https://club.perfma.com/article/233480
正文到此结束
Loading...