Java 的 JIT 知识整理
JIT 介绍
JIT 编译器(just in time 即时编译器),当虚拟机发现某个方法或代码块运行特别频繁时,就会把这些代码认定为 热点代码(Hot Spot Code),为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各层次的优化,完成这项任务的正是 JIT 编译器。
JIT 的工作原理
JIT 编译
对于 Java 代码,刚开始都是被编译器编译成字节码文件
,然后字节码文件会被交由 JVM 解释执行
,所以可以说 Java 本身是一种半编译半解释执行的语言。
当 JIT 编译启用时(默认是启用的),JVM 读入.class文件解释后
,将其发给JIT编译器
。JIT 编译器将字节码编译成本机机器代码
。
通常 Javac将程序源码编译
,转换成java字节码
,JVM通过解释字节码将其翻译成相应的机器指令
,逐条读入,逐条解释翻译。
经过解释运行,其运行速度必定会比可运行的二进制字节码程序慢。为了提高运行速度,引入了 JIT 技术。
在执行时 JIT 会把翻译过的机器码保存起来,已备下次使用,因此从理论上来说,采用该 JIT 技术能够,能够接近曾经纯编译技术。
运行过程中会被即时编译器编译的热点代码
有两类:被多次调用的方法、被多次调用的循环体。
这两种情况,编译器都是以整个方法作为编译对象,这种编译也是虚拟机中标准的编译方式。要知道一段代码或方法是不是热点代码,是不是需要触发即时编译,需要进行 Hot Spot Detection(热点探测)
。
热点判定方式
目前主要的热点 判定方式有以下两种:
- 基于采样的热点探测:
采用这种方法的虚拟机会周期性地检查各个线程的栈顶,如果发现某些方法经常出现在栈顶,那这段方法代码就是 “热点代码”。这种探测方法的好处是实现简单高效,还可以很容易地获取方法调用关系,缺点是很难精确地确认一个方法的热度,容易因为受到线程阻塞或别的外界因素的影响而扰乱热点探测。 - 基于计数器的热点探测:
采用这种方法的虚拟机会为每个方法,甚至是代码块建立计数器,统计方法的执行次数,如果执行次数超过一定的阀值,就认为它是 “热点方法”。这种统计方法实现复杂一些,需要为每个方法建立并维护计数器,而且不能直接获取到方法的调用关系,但是它的统计结果相对更加精确严谨。
HotSpot 虚拟机中使用的是第二种: 基于计数器的热点探测方法,因此它为每个方法准备了两个计数器:方法调用计数器和回边计数器
。
方法调用计数器
方法调用计数器用来统计方法调用的次数,在默认设置下,方法调用计数器统计的并不是方法被调用的绝对次数,而是一个相对的执行频率,即一段时间内方法被调用的次数。
回边计数器
用于统计一个方法中循环体代码执行的次数(准确地说,应该是回边的次数,因为并非所有的循环都是回边),在字节码中遇到控制流向后跳转的指令就称为 “回边”。
方法调用计数器触发即时编译的流程:
设置热点阈值
一段代码需要执行多少次才会触发 JIT 优化呢?通常这个值由 -XX:CompileThreshold
参数进行设置:
使用 client 编译器时,默认为 1500。
使用 server 编译器时,默认为 10000。
JIT 优化
开启服务端模式
开启服务端模式以后就有即时编译器和解释器两种执行引擎,执行效率最高的是即时编译器,所以我们做 JIT 优化的目的是尽量使代码使用即时编译器
参数设置:
-server
增加内联函数的可能性
增加函数内联的可能性能减少栈帧的创建,节约内存空间
参数设置:
使用 final 修饰函数向编译器建议可以内联,启动参数不宜设置,注意只是建议,具体是否内联看 JVM 决定
提高使用即时编译器的可能性
小方法:写方法时尽量不要写得太大,让 JVM 尽可能使用即时编译器编译代码
在启动项配置参数 - XX:CompileThreshold=10000,使得一个方法被调用超过 10000 次以后使用即时编译器编译为机器码
OSR 编译阈值
- 调用计数器,即方法被调用的次数,CompileThreshold,该值是指当方法被调用多少次后,就编译为机器码,client 模式默认为 1500 次,server 模式默认为 1 万次,可以在启动时添加 - XX:CompileThreshold=10000 来设置该值。
- 回边计数器,即方法中循环执行部分代码的执行次数,OnStackReplacePercentage,该值用于 / 参与计算是否触发 OSR 编译的阈值,client 默认为 933,sever 默认为 140,可以通过 - XX:OnStackReplacePercentage=140 来设置。
client 模式下的计算规则为:
CompileThreshold*OnStackReplacePercentage/100
server 模式下计算规则为:
CompileThreshold *(OnStackReplacePercentage-InterpreterProfilePercentage)/100
InterpreterProfilePercentage,默认为 33。
降低线程优先级
Linux 不能设置,需要 root 权限
热度衰减与半衰周期