译:谁是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的对比如下图所示:
benchmark result
结果非常清晰,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这种测试用例。

引用链接

  • 参考[1]:https://www.optaplanner.org/blog/2015/07/31/WhatIsTheFastestGarbageCollectorInJava8.html
  • 参考[2]:https://stackoverflow.com/questions/46377561/why-g1-is-default-garbage-collector-for-java-9
  • 参考[3]: http://openjdk.java.net/jeps/248
本站所有文章均由网友分享,仅用于参考学习用,请勿直接转载,如有侵权,请联系网站客服删除相关文章。若由于商用引起版权纠纷,一切责任均由使用者承担
极客文库 » 译:谁是JDK8中最快的GC

Leave a Reply

欢迎加入「极客文库」,成为原创作者从这里开始!

立即加入 了解更多