前言
本文尝试用浅显的语言, 解释JIT的概念和基本原理,让读者明白JIT的运行方式和作用。最后,附上关于JIT的代码样例,帮助大家更好理解JIT。本文使用JVM虚拟机为Hotspot ,一切分析都在Hotpot上。如有不对的地方,欢迎指正。
JIT简介
JIT 是just in time 的缩写,即时编译编译器。
当JIT编译启用时, JVM读入字节码文件解释后,将其发给JIT编译器。JIT编译器将字节码编译成本机机器代码。
java代码编译过程
源代码经过javac编译,转换成java字节码文件(.class文件)。之后,JVM解释器将字节码文件翻译成对应的机器指令,逐条读入,逐条解释翻译。最后,对应的OS执行机器指令。
java code 需要经过解释执行,所以它的速度必然比可执行的二进制字节码程序慢。
Created with Raphaël 2.1.0 源码 javac 是否需要JIT编译? JIT编译器 机器码 解释器 yes no
JIT编译
通常,JVM执行代码时, 它并不立即开始JIT编译。
主要有两点原因
- 代码本身只会被执行一次,那么从效率上来讲,对它进行JIT编译就是在浪费精力。因为JIT编译相对于解释器翻译字节码开销要大的多。
- 当代码执行的次数越多, JIT就会越了解代码结构。JIT对代码的优化会更好。
那么什么时候会对代码进行JIT编译?
一般来说,当代码被频繁调用时(通常场景,代码中的循环体),该代码块将被JIT编译器编译。
JIT的编译阈值
在JVM中有两个计数器:
- 方法被调用的次数
- 方法中循环被回弹执行的次数
JVM在执行java方法时,它会坚持这两个计数器总和来决定是否需要JIT编译。
*-XX:CompileThreshold=N 可以配置这个阈值。在server模式下,它的默认值是10000;在client模式下,默认值是1500。
PS:不同的模式使用的JIT编译器是不同的。在client模式下, JVM使用的是一个代号为C1的轻量级编译器,而在server模式下,使用的是代号C2相对重量级的编译器。与C1相比,C2编译的更彻底,所以服务起来后,性能更高 *
2
JIT的优化策略
JIT的优化策略有很多,这里主要介绍两种比较普遍的优化,帮助大家理解JIT理解JIT优化的细节。
- 使用寄存器优化
编译器通过决定何时从主存取值,何时向寄存器充值,来优化程序执行,减少开销。
Java Code 的例子
public class RegisterTest {
private int sum;
public void calculateSum(int n) {
for (int i = 0; i < n; ++i) {
sum += i;
}
}
}
在上面的例子中,sum的值在某些时刻可能在主存中,但是从主存中检索值是开销很大的操作。循环中可能多次从主存取值,性能很低。JIT编译器寄存器优化策略,会加载一个寄存器给sum并赋予其初始值,sum值在寄存器里循环,循环结束后,将最终的结果从寄存器返回给主存。这样就省去了从主存中检索值得开销。
- 使用方法内联优化
方法内联就是把方法的代码“复制”到发起调用的方法里,以消除方法调用。
Java Code 的例子
public void caller(){
int a=1;
int b=2;
//do sth
int result =sum(a,b);
}
public int sum(int x,int y){
return x+y;
}
经过JIT编译器优化后,可能是转换为
public void caller(){
int a=1;
int b=2;
//do sth
int result =a+b;
}
PS:以上例子只是帮助理解,并不能准确的反映JIT的方法内联优化。
JIT编译器优化实践
下面是一个简单的JIT优化的例子
package test.jit;
/**
* -XX:CompileThreshold=100000
* @author marshall
*
*/
public class JITTest {
public static int sum(int x, int y) {
int a = x + 1;
int b = y + 1;
int rs = a + b;
return rs;
}
public static int total(int n) {
int res = 0;
for (int i = 0; i < n; i++) {
res += sum(i, i);
}
return res;
}
public static void main(String[] args) {
int rs;
long bf;
long af;
long beforeJIT;
long afterJIT;
bf = System.nanoTime();
rs = total(100000);
af = System.nanoTime();
beforeJIT = af - bf;
System.out.println("程序没有经过预热即没有经过JIT优化,花费时间:" + beforeJIT + " 纳秒");
bf = System.nanoTime();
rs = total(100000);
af = System.nanoTime();
afterJIT = af - bf;
System.out.println("程序经过预热即经过JIT优化,花费时间:" + afterJIT + " 纳秒");
System.out.println("减少开销:" + (beforeJIT - afterJIT) / (beforeJIT * 1.0));
}
}
手动设置程序阈值为100000,经过多次测试,运行测试越多, JIT优化性能提升越大。