毕设背景
时至今日,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:应用程序总体性能的提升
文稿:李浩宇
编辑:高宇岑
领取专属 10元无门槛券
私享最新 技术干货