执行引擎基本概念
执行引擎是java虚拟机的核心部分之一
物理机的执行引擎是直接建立在处理器、缓存、指令集和操作系统上的,虚拟机的执行引擎则是由软件自行实现的。所以可以不受物理条件制约,来定制指令集与执行引擎的结构,能够执行不被硬件直接支持的指令集,但是效率是不如物理机的
jvm的主要任务就是加载字节码文件,但是字节码并不能直接运行在操作系统,所以就需要执行引擎把字节码指令解释、编译为对应平台的本地机器指令,简单来说,执行引擎就是把高级语言翻译为机器语言的翻译官
大部分程序代码都需要经历上面的过程
生成字节码文件的过程称为前端编译,把字节码文件编译成机器指令称为后端编译
上图黄色是生成字节码文件的过程
绿色部分是解释的过程
- 解释器:当java虚拟机启动时会根据预定义的规范对字节码采用逐行解释的方式执行,将每条字节码文件中的内容 翻译 为对应平台的本地机器指令执行(逐条翻译,直接执行)
蓝色部分是编译的过程
- JIT 编译器:虚拟机将源代码直接编译成和本地机器平台相关的机器语言
java是半编译半解释的语言,就是执行引擎在执行时,既可以使用编译器执行,又可以使用解释器执行
java在1.0时,只有解释执行,后来才引入的编译器,现在通常都是二者结合起来使用
- 机器语言就是二进制的指令(0,1),它的执行效率是最高的,但是可读性很差,
- 所以就发明了指令,也就是把特定的0、1序列简化为指令,可读性较好,但是不同平台的指令可能不同
- 由于指令集的可读性依然很差,就有了汇编语言,汇编也不能直接执行,也需要翻译
- 然后就是高级语言了,比较接近人类的语言
对于硬件来说,只认识机器指令,高级语言也必须翻译为汇编,然后在翻译为机器指令,高级语言不能直接翻译为机器语言
解释器
如果只是为了跨平台,那么直接把代码翻译成汇编语言,再把汇编语言翻译为机器指令也是可以实现跨平台,但是这样对虚拟机的要求会非常高,而引入字节码文件,可以降低虚拟机的要求,还能防止源代码泄露,而且字节码可以跨语言
解释器其实就是一个 翻译者 的角色,将字节码文件的内容翻译为对应平台的机器指令,当一条字节码指令被解释执行完成以后,接着在根据pc寄存器中记录的下一条需要被执行的字节码指令执行解释操作
解释器是很多语言中都存在的,但是解释器的效率通常较低
为了解决这个问题,jvm支持了即时编译技术,目的是避免函数被解释执行,而是将整个函数体编译为机器码,每次函数执行时,只执行编译后的机器码即可,这种及时编译技术大幅度提高了执行效率
编译器
JIT编译器是hotspot执行的一种方式。
- 相对于解释器执行效率更高
- 但是解释器执行代码可以立即开始执行,响应速度块,而编译器需要时间去编译,响应时间长
hotspot在虚拟机启动时,解释器首先发挥作用,不必等到即时编译器全部编译完成在执行,这样可以省去很多不必要的等待时间,并且随着程序运行时间的推移,即时编译器逐渐发挥作用,根据热点探测功能把有价值的字节码编译为本地机器指令,以换取更高的执行效率,另外解释器还可以作为备选方案
而 JRockit 就没有解释器,所以启动时间会很长,但是执行效率更高,因为对于服务器端来说,启动时间并不重要
热点代码及探测方式
是否需要使用 jit编译器把字节码编译为对应平台的本地机器指令,需要根据代码被调用的执行频率决定,频率高的代码就叫做热点代码,对于热点代码就可以做出深度优化,将其直接编译为机器指令,提升java程序的执行性能
- 一个被多次调用的方法,或者方法体内循环多次的循环体都可以被称为热点代码
这种编译方式发生在执行过程中,所以被称为栈上替换,简称为: OSR
hotspot所采用的是基于计数器的热点探测,目前计数器有下面两个
- 方法调用计数器,统计方法的调用次数,在Client 模式下默认是1500次,在Server模式下,默认是10000次,超过这个阈值就会触发jit编译,但是只要执行的时间足够长,那几乎所有的代码都有可能会被即时编译,所以需要有热度衰减
- 如果不做设置,方法调用计数器统计的并不是被调用的绝对次数,而是相对的执行频率,也就是在一定时间内的被调用次数,所以当超过一定的时间限度,方法调用次数任然不足以让它触发即时编译,那么方法调用计数器的数值就会会减少一半,这个过程被称为热度衰减,这段时间被称为半衰周期
- 半衰周期可以设置,热度衰减也可以关闭
- 回边计数器,统计方法体内循环多次的循环体
方法调用计数器:
hotspot 可以手动程序设置执行方式,默认是混合模式
- -Xint:完全解释器模式执行
- -Xcomp:完全采用即时编译器执行,如果即时编译器出现问题,解释器会介入执行
- -Xmixed: 混合模式
c1 、c2
hotspot虚拟机内嵌了两个jit编译器:Client Complier、Server Complier ,简称为c1 、c2
- -client :指定虚拟机在Client模式下运行,使用c1编译器,c1编译器会对字节码进行简单和可靠的优化,耗时短
- -server : 指定虚拟机在Server模式下运行,使用c2编译器,优化时间较长,但执行效率更高,64位操作系统默认就是sever模式
c1、c2编译器的优化策略:
c1主要有:方法内联、去虚拟化、冗余消除
- 方法内联:将引用函数的代码编译到引用点处,可以减少栈帧的生成,减少参数传递以及跳转的过程
- 去虚拟化:对唯一的实现类进行内连
- 冗余消除:运行期间把不会执行的代码折叠掉
c2优化为:在逃逸分析的基础上,变量替换、栈上分配、同步消除
在jdk1.7之后,c1 、c2 是可以相互协同使用的
AOT
jit是在程序运行过程中执行的,aot是提前编译器(jdk9引入的),在程序执行之前进行一些优化,可减少java给人第一次运行起来慢的印象
但是缺点是字节码文件就不能跨平台了(生成的机器指令不跨平台),降低了java链接过程的动态性,且目前只支持64位的linux