JIT全称:Just in time。中文译为:即时的、实时的。
JVM中的这项技术名为:实时编译技术,也叫即时编译技术。就是在java程序运行的过程中,将字节码编译为机器码运行在本地,而不是通过JVM解释运行(字节码都是通过JVM解释运行的)。
我们先来思考一个问题,java代码是如何“跑起来”的?
我们编写好的java代码,通过点一下IDE中的运行按钮,就看到程序运行起来的样子了,那么这一过程发生了什么呢,来看下:
第一步:javac工具将.java文件编译成.class文件;
第二部:jvm将.class文件(.class文件中的内容都是字节码,字节码由opcode和操作数组成)加载到jvm内存中,并逐行运行其中的指令。这里的运行是解释运行的,需要通过解释器将字节码指令转换成机器码(汇编)并运行。
由于解释运行的效率不如直接运行机器码,那么jvm为什么不把字节码指令全部编译成机器码然后运行呢,也就是不需要jvm了,只需要一个把字节码编译成机器码的编译器即可。
但是把所有字节码都编译成机器码是很费时的,导致java程序启动很缓慢。
所以JIT支持三种情况:混合模式(Mixed Mode),解释模式(Interpreted Mode),编译模式(Compiled Mode)。
用户在运行java程序时可以通过参数自行选择,默认是混合模式。
解释模式:java -Xint
编译模式:java -Xcomp
下面我们正式开始介绍编译部分:
Java中内置了两个即时编译器有两个:clientCompiler 和 serverCompiler,简称为C1和C2(也叫Opto编译器)。
clientCompiler 更注重编译的效率,追求快速完成编译,对代码进行简单、可靠的编译。优点:编译速度快;缺点:编译质量不如C2,因为只是基于代码的静态编译。C2是动态编译。(关于静态编译和动态编译的对比,可以参考《静态编译&动态编译》)
serverCompiler 更注重编译的质量,追求最大限度优化代码运行效率。会根据监控(Profiling)收集上来的信息进行优化。比如:热点代码检测。优点:编译质量高;缺点:编译速度慢。
为了达到一个平衡,JDK1.7开始就有了分层编译(Tiered Compilation),根据编译器编译、优化的规模与耗时,划分出不同的编译层次,包括:
第0层:程序解释执行,不开启性能监控(Profiling),可触发第1层编译。
第1层:也称为C1编译,将字节码编译成机器码,进行简单、可靠的优化,如有必要将加入性能监控的逻辑。
第2层(或以上):也称为C2编译,将字节码编译成机器码,但是会启用一些编译耗时较长的优化,甚至会根据性能监控信息进行一些不可靠的激进优化。
也可以更加详细的分为:
复制代码
第 0 层:程序解释执行,默认开启性能监控功能(Profiling),如果不开启,可触发第二层编译;
第 1 层:可称为 C1 编译,将字节码编译为本地代码,进行简单、可靠的优化,不开启 Profiling;
第 2 层:也称为 C1 编译,开启 Profiling,仅执行带方法调用次数和循环回边执行次数 profiling 的 C1 编译;
第 3 层:也称为 C1 编译,执行所有带 Profiling 的 C1 编译;
第 4 层:可称为 C2 编译,也是将字节码编译为本地代码,但是会启用一些编译耗时较长的优化,甚至会根据性能监控信息进行一些不可靠的激进优化。
复制代码
这里提到了不可靠的优化,是的,不是所有的优化都提升程序的运行效率。有时候优化了还不如不优化,当编译器发现优化后的代码运行效率不如之前,或者出现“罕见陷阱(Uncommon Trap)”时,可以通过逆优化(Deoptimization)退回到优化前的状态继续执行。
要退回的那个状态就是“逃生门”,解释运行就是C1的逃生门,C1就是C2的逃生门。
Tips:编译动作是异步的,不会阻塞代码运行,如果一个方法在编译中,那么就会解释执行。