持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第22天,点击查看活动详情
前面一章讲解了编译器中代码缓存的优化,代码缓存的大小对jvm的性能存在着一定的影响。不同的jvm版本对应着不同的代码缓存默认值,常规情况我们是不需要自己设定的,但是某些大型项目,需要我们手动去优化代码缓存的大小。同时我们可以通过jconsole去动态的监视代码缓存的使用情况。
编译阈值
本章继续学习编译器相关的内容。前面我们说过,jvm是即时编译器,将应用代码编译成jvm识别的特定代码,然后通过编译器解释编译成服务器能够识别的汇编语言。但是在代码运行阶段,通过server编译器会对变热的代码进行再次编译,同时优化代码逻辑,防止其重复的解释,让其获得更好的性能。
那么,何时开始对热点代码进行编译呢? 换句话说,热点代码编译的条件是什么?
重点就是编译阈值
,当一段代码运行达到一定的次数,编译器获得了关于这些代码的足够的信息,就会触发编译。
通常来说,是不需要人为干预编译阈值的。
编译器的工作原理
编译器的工作基于JVM的计数器:
方法调用计数器
循环回边计数器
:记录循环执行的次数
每当调用方法的时候,都会检查线程的这两种计数器的数量,看其是否符合编译条件,符合条件的方法会被加入编译队列
。
针对循环体的代码,则是另一种情况。如果循环一直运行,不会退出,则每次循环完成都会在循环会变计数器记录一次循环次数,当其达到阈值时,也会进行编译。
上面描述的过程被称为栈上替换
,当循环被编译完成后,jvm会替换还在粘上的代码,运行编译后的循环代码。
注意:jvm当中,方法的运行是在虚拟机栈当中,每个线程独有自己的虚拟机栈。
编译阈值优化
在jvm当中,有特定的标志符号表示编译阈值:CompileThreshold
,使用如下方式可以查看默认值:
[root@hecs-402944 ~]# jps
5669 Jps
13577 jar
655 WrapperSimpleApp
[root@hecs-402944 ~]# jinfo -flag CompileThreshold 13577
-XX:CompileThreshold=10000
复制代码
如上所示,在64位的java8当中,默认值是10000,即sever编译器的默认值,前面提到过,java8默认是server编译器。
默认值:
如果降低这个默认值,可能会发生什么?
前面我们说过,当一段代码运行一定次数就会变热,达到阈值就会被编译,如果调低阈值,那么会导致下面两个结果:
- 热身时间变短
- 原本不会被编译的代码,可能被编译。
为什么说是可能
?
不会被编译的代码,并不全是因为运行次数达不到阈值,而是计数器的运行次数会周期性减少
。由于这个机制,可能导致一些代码永远不会被编译,即使是一直在运行的代码,它们只能被称作温热
。
解决温热代码不能被编译的方式,就是降低编译阈值
。
分层编译会比server编译要稍快一些,为什么?
分层编译是先通过 client编译器 将代码编译,然后再运行当中使用server编译器对热点代码进行编译。而由于温热代码的存在,当只是用server编译器时,这部分温热代码是没有被编译的,运行速度慢。在分层编译中,client编译器已经将他们编译过了。