一个代码长度引发的血案

是的,你没有看错,Java代码段中方法的长度是会影响代码性能的。

问题情况

最近对代码进行性能测试时发现一个很诡异的问题,在低并发的情况下,多线程的性能增长不是线性的,对问题定位后发现奇怪的现象:

当一个方法字节码索引号超过8000时,每个线程执行该代码段时会带来额外的几十毫秒开销,最终测试结果如下:

多线程下有问题的测试结果

当我把该方法中任意部分抽出一个方法后,测试结果如下:

多线程下没问题的测试结果

而我做的仅仅是把任意一行代码抽出了一个方法,并在原位置调用它:

抽出来的代码

这两个版本的区别仅在于字节码少了3行:

这个方法的行数:
这个方法的行数

这个类的字节码文件总行数:
这个类的字节码文件总行数

唯一的区别:
唯一的区别

将方法中任意代码段抽出,都能解决这个问题。且这个问题仅在多线程下复现。(起码两个线程)

具体原因

HotSpot VM默认不会JIT编译字节码大小超过8000字节的方法。

HotSpot有两个参数控制这一行为:

-XX:+DontCompileHugeMethods
-XX:HugeMethodLimit=8000

DontCompileHugeMethodsHugeMethodLimit的值在globals.hpp中定义:

define_pd_global(intx, ReservedCodeCacheSize, 48M);
product_pd(uintx, InitialCodeCacheSize, “Initial code cache size (in bytes)”)
product_pd(uintx, ReservedCodeCacheSize, “Reserved code cache size (in bytes) - maximum code cache size”)
product(uintx, CodeCacheMinimumFreeSpace, 500K, “When less than X space left, we stop compiling.”)

如果方法字节码超过8000字节,默认情况下JVM是不会对这方法进行JIT编译的,也就享受不到JIT编译带来的性能优化了。

解决方案

由于HugeMethodLimit的值在产品版HotSpot里无法调整,

临时的解决办法是使用-XX:-DontCompileHugeMethods参数来允许大方法被JIT编译,不过使用这个参数后要注意配置CodeCache区的大小,以免满了之后HotSpot停止所有后续的编译任务。

一劳永逸的办法当然是把大段的代码抽成一段段小逻辑啦。

感想总结

2017-12-01 20:17