多核环境下可伸缩的Java垃圾收集器的设计与实现

毕设背景

时至今日,Java已经成为了编程世界中一门重要的语言,许多的应用程序、计算框架与系统都使用了 Java 来进行开发,而它们的运行都依赖于Java 虚拟机(Java Virtual Machine, JVM)这一底层环境。以垃圾收集(Garbage Collection, GC)为代表的自动化内存管理是JVM中的一项重要特性,它不仅可以减少人们编程时的负担,还能有效减少内存泄漏等问题的发生。然而 Java 中的GC大多采用Stop-the-World的方式进行,即应用程序在GC期间完全暂停,直到GC结束再恢复执行。这样做的好处在于GC与应用之间无需协调、可以达到较高的GC吞吐量,但缺点则是GC会造成应用程序停顿,以致于应用程序的性能降低。

分析与思考

从上文提到的这一问题出发,我在毕业设计中对Java现有的Full GC机制进行了分析,我发现Full GC的性能问题很大程度上源于它的线程利用率不高,表现为在多核环境中的可伸缩性有限,具体来说,Full GC的吞吐量往往在4核左右便达到了性能峰值,之后随着CPU核数的增加,GC的性能并不能增加,反而出现停滞甚至下降。因此,在毕业设计中我通过提升GC在多核环境下的可伸缩性这一方法,优化了Java Full GC的性能,并最终提升了应用程序的性能。

通过进一步的分析,我发现Full GC可伸缩性差的原因在于GC过程中,Java堆中的各个Region之间存在严重的依赖关系。由于Full GC采用的是Mark-Compact算法,所以每一个Region在开始GC之前都需要等待它的Destination Region先完成GC,这样的依赖关系降低了GC线程间的并行度,严重地制约了Full GC的可伸缩性。

优化与改进

为了减少Region间的依赖关系,我提出了两项优化:Shadow Region 与 Region Skip。前者通过在Full GC过程中动态地创建 Shadow Region 充当临时的Destination用于存放数据,从而消除 Region与Destination 之间的依赖关系,以达到提高 GC 线程利用率的目的。后者则在 Full GC 时检测 Java 堆中的满载Region,跳过这部分Region不对其进行GC,即保持其中的对象不被移动,这使得存在依赖的Region变少,同时也减少了Full GC的工作量,进而提升了Full GC的效率。两项优化的大体思路可以参照以下两张图。

图1:Shadow Region思路示意图

图2:Region Skip思路示意图

性能测试

使用原生JVM与已实现了上述两项优化的JVM进行了性能测试比较。性能测试的指标包括GC线程的利用率、Full GC的可伸缩性、Full GC在16核时的吞吐量以及Benchmark的总体运行时间。测试结果显示,经过优化的 Full GC在GC线程利用率上提升了16.5倍,并且可以线性地伸缩至8核,同时之后仍保持伸缩,此外在16核时吞吐量最高提升3.9倍,最低1.9倍,平均值达到2.9倍,而Benchmark的总体运行时间平均缩短了19.3%。总体来说优化效果还是比较明显的。

图3:原生Full GC中Compact阶段各GC线程的利用率

图4:优化之后Full GC中Compact阶段各GC线程的利用率

图5:Full GC可伸缩性曲线图

图6:Full GC吞吐量的提升

图7:应用程序总体性能的提升

文稿:李浩宇

编辑:高宇岑

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180723G0N9Y600?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券