什么是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技术能够,能够接近曾经纯编译技术。
现在主流的商用虚拟机(如Sun HotSpot、IBM J9 如 # 各大主流的虚拟机比较
)中几乎都同时包含``解释器
和编译器
(三大商用虚拟机之一的JRockit是个例外,它内部没有解释器,因此会有启动相应时间长之类的缺点,但它主要是面向服务端的应用,这类应用一般不会重点关注启动时间)。
二者各有优势:当程序需要迅速启动和执行时,解释器可以首先发挥作用,省去编译的时间,立即执行;当程序运行后,随着时间的推移,编译器逐渐会返回作用,把越来越多的代码编译成本地代码后,可以获取更高的执行效率。解释执行可以节约内存,而编译执行可以提升效率。
HotSpot虚拟机中
内置了两个JIT编译器:Client Complier
和Server Complier
,分别用在客户端和服务端,目前主流的HotSpot虚拟机中默认是采用解释器与其中一个编译器直接配合的方式
工作。
运行过程中会被即时编译器编译的热点代码
有两类:被多次调用的方法。``被多次调用的循环体。
这两种情况,编译器都是以整个方法作为编译对象
,这种编译也是虚拟机中标准的编译方式。要知道一段代码或方法是不是热点代码,是不是需要触发即时编译,需要进行Hot Spot Detection(热点探测)
。
目前主要的热点 判定方式有以下两种:
- 基于采样的热点探测:
采用这种方法的虚拟机会周期性地检查各个线程的栈顶,如果发现某些方法经常出现在栈顶,那这段方法代码就是“热点代码”。这种探测方法的好处是实现简单高效,还可以很容易地获取方法调用关系,缺点是很难精确地确认一个方法的热度,容易因为受到线程阻塞或别的外界因素的影响而扰乱热点探测。 - 基于计数器的热点探测:
采用这种方法的虚拟机会为每个方法,甚至是代码块建立计数器,统计方法的执行次数,如果执行次数超过一定的阀值,就认为它是“热点方法”。这种统计方法实现复杂一些,需要为每个方法建立并维护计数器,而且不能直接获取到方法的调用关系,但是它的统计结果相对更加精确严谨。
HotSpot虚拟机中使用的是第二种:基于计数器的热点探测方法
,因此它为每个方法准备了两个计数器:方法调用计数器和回边计数器
。
-
方法调用计数器
方法调用计数器用来统计方法调用的次数,在默认设置下,方法调用计数器统计的并不是方法被调用的绝对次数,而是一个相对的执行频率,即一段时间内方法被调用的次数。
-
回边计数器
用于统计一个方法中循环体代码执行的次数(准确地说,应该是回边的次数,因为并非所有的循环都是回边),在字节码中遇到控制流向后跳转的指令就称为“回边”。
JIT编译。触发了JIT编译后,在默认设置下,执行引擎并不会同步等待编译请求完成,而是继续进入解释器按照解释方式执行字节码,直到提交的请求被编译器编译完成为止(编译工作在后台线程中进行)。当编译工作完成后,下一次调用该方法或代码时,就会使用已编译的版本。
注: Javac字节码编译器与虚拟机内的JIT编译器的执行过程合起来其实就等同于一个传统的编译器所执行的编译过程。
Java和C/C++的编译器对比
这里不是比Java和C/C++谁快这种大坑问题,只是比较编译器(我个人认为开发效率上Java快,执行效率上C/C++快)
这种对比代表了经典的即时编译器与静态编译期的对比,其实总体来说Java编译器有优有劣。主要就是动态编译时间压力大能做的优化少,还要做一些动态校验。而静态编译器无法实现一些开发上很有用的动态特性。
参考文献: