Java性能优化--编译阈值优化

语言: CN / TW / HK

theme: devui-blue highlight: a11y-dark


持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第22天,点击查看活动详情

前面一章讲解了编译器中代码缓存的优化,代码缓存的大小对jvm的性能存在着一定的影响。不同的jvm版本对应着不同的代码缓存默认值,常规情况我们是不需要自己设定的,但是某些大型项目,需要我们手动去优化代码缓存的大小。同时我们可以通过jconsole去动态的监视代码缓存的使用情况。

编译阈值

本章继续学习编译器相关的内容。前面我们说过,jvm是即时编译器,将应用代码编译成jvm识别的特定代码,然后通过编译器解释编译成服务器能够识别的汇编语言。但是在代码运行阶段,通过server编译器会对变热的代码进行再次编译,同时优化代码逻辑,防止其重复的解释,让其获得更好的性能。

那么,何时开始对热点代码进行编译呢? 换句话说,热点代码编译的条件是什么?

重点就是编译阈值,当一段代码运行达到一定的次数,编译器获得了关于这些代码的足够的信息,就会触发编译。

通常来说,是不需要人为干预编译阈值的。

编译器的工作原理

编译器的工作基于JVM的计数器:

  • 方法调用计数器
  • 循环回边计数器:记录循环执行的次数

每当调用方法的时候,都会检查线程的这两种计数器的数量,看其是否符合编译条件,符合条件的方法会被加入编译队列

针对循环体的代码,则是另一种情况。如果循环一直运行,不会退出,则每次循环完成都会在循环会变计数器记录一次循环次数,当其达到阈值时,也会进行编译。

上面描述的过程被称为栈上替换,当循环被编译完成后,jvm会替换还在粘上的代码,运行编译后的循环代码。

注意:jvm当中,方法的运行是在虚拟机栈当中,每个线程独有自己的虚拟机栈

编译阈值优化

在jvm当中,有特定的标志符号表示编译阈值:CompileThreshold,使用如下方式可以查看默认值: bash [[email protected] ~]# jps 5669 Jps 13577 jar 655 WrapperSimpleApp [[email protected] ~]# jinfo -flag CompileThreshold 13577 -XX:CompileThreshold=10000

如上所示,在64位的java8当中,默认值是10000,即sever编译器的默认值,前面提到过,java8默认是server编译器。

默认值:$CompileThreshold = 方法调用计数 + 循环回边计数$

如果降低这个默认值,可能会发生什么?

前面我们说过,当一段代码运行一定次数就会变热,达到阈值就会被编译,如果调低阈值,那么会导致下面两个结果: * 热身时间变短 * 原本不会被编译的代码,可能被编译。

为什么说是可能

不会被编译的代码,并不全是因为运行次数达不到阈值,而是计数器的运行次数会周期性减少。由于这个机制,可能导致一些代码永远不会被编译,即使是一直在运行的代码,它们只能被称作温热

解决温热代码不能被编译的方式,就是降低编译阈值

分层编译会比server编译要稍快一些,为什么?

分层编译是先通过 client编译器 将代码编译,然后再运行当中使用server编译器对热点代码进行编译。而由于温热代码的存在,当只是用server编译器时,这部分温热代码是没有被编译的,运行速度慢。在分层编译中,client编译器已经将他们编译过了。