执行引擎是JVM核心组成之一,由于操作系统只能识别机器指令,想要在机器上执行程序,不管什么语言最终都需要转换成机器指令。
JVM中的执行引擎主要将字节码指令转换为机器指令并执行
执行引擎的工作流程:
1.执行引擎执行过程中是根据程序计数器中存储的指令地址来执行对应的指令
2.每当执行引擎执行完一条指令
程序计数器就会更新下一条需要被执行的指令地址
3.当方法执行的过程中,执行引擎可能会通过存储在局部变量表中的引用变量访问堆中对应的对象
已经通过对象头中的类型指针找到元空间中对应的类型信息。
Java代码的编译器有两种,一种是前端编译器,一种是后端编译器
前端编译器,如javac,即将java文件编译成class文件:
后端编译器,即将class文件中的字节码指令编译成本地机器指令:
解释器:
当JVM启动时会根据预定义的规范对字节码采用逐行解释的方法执行
将每条字节码指令编译成对应的本地机器指令并执行
JIT编译器(即时编译器):
将字节码指令直接编译成本地机器指令,只负责热点代码的编译工作
默认情况下,HotSpot VM采用解释器与JIT编译器并存的架构,开发者可以通过参数设置完全使用解释器,还是完全使用JIT编译器
-Xint 完全使用解释器来执行程序
-Xcomp 完全使用JIT编译器 如果编译出错 解释器会介入执行
-Xmixed 解释器+JIT编译器共同执行程序(默认)
解释器实际上是Java字节码的一个翻译者,它会将字节码指令编译成本地机器指令并执行.
在Java发展的历程里,一共有两套解释执行器,古老的字节码解释器,和现在普遍使用的模板解释器
字节码解释器:
通过纯软件代码模拟字节码的执行,效率非常低
模板解释器:
每一条字节码和一个模板函数相关联
模板函数能直接生产这条字节码执行时的机器码
在HotSpot VM中,解释器主要由Interpreter模块和Code模块构成
Interpreter模块: 实现解释器的核心功能
Code模块: 管理HotSpot VM运行时生成的本地机器指令
由于解释器在设计和实现上非常简单,除Java外,Python,Perl,Ruby等也是基于解释器执行的,基于解释器执行已经沦落为低效的代名词,为了解决这个问题,JVM提供了即时编译技术
Java代码执行过程有两种分类:
一类是将源代码编译成字节码文件,然后在运行时通过解释器将字节码文件逐行转为机器码执行
一类是直接将字节码编译为机器码
,不用一行一行进行执行。
即时编译的目的是避免方法被解释执行,将整个方法编译成机器码,每次执行该方法时,只执行经过即时编译后的机器码,这种方式可以使执行效率大幅度提升
虽然解释器效率较低,JIT编译器效率较高,但是这二者在一起工作比起单纯使用JIT编译器的效果更好
因为当程序启动后,解释器可以立刻执行,响应速度快
而JIT编译器需要将热点代码编译成机器指令,判断热点代码需要一定的时间尽管JRockit VM中没有解释器,字节码全部靠JIT编译器编译后执行
但为此程序的启动一定需要更多的时间,对于服务端应用而言启动时间不是重点,而对于看重启动时间的应用场景中,不能全靠JIT编译完后再执行,启动会很慢
JDK10之前在HotSpot VM中内嵌两个JIT编译器,分别为Client Compiler(C1编译器)和Server Compiler(C2编译器),开发者可以通过命令来设置JVM运行时使用哪种JIT,但64位机器只能使用Server Compiler
-client 指定使用C1编译器
C1编译器会对字节码进行简单和可靠的优化,耗时短
以达到更快的编译速度
-server 指定使用C2编译器
C2编译器会进行更长时间的优化,以及采取更激进的优化策略,耗时长
使得编译后的代码执行效率更高
C1编译器的优化策略:
方法内联
去虚拟化
冗余消除
C2编译器的优化策略:
标量替换
栈上分配
同步消除
当某段字节码指令是否需要被JIT编译器编译成机器指令,是需要根据这段代码被调用执行的频率决定,高频被调用的代码也称为热点代码,JIT编译器在运行期间会对热点代码做出深度优化,并将其编译成机器指令缓存到方法区的Code Cache,以提升Java程序的执行性能
热点代码:
一个被多次调用的方法,或者一个方法体内部循环较多的循环体
都可以称为热点代码,通过JIT编译成机器指令
一个方法需要被执行多少次,或一个循环体要执行多少次循环才能算热点代码,这必然需要一个明确的阈值,这个阈值在C1中是1500,在C2中是10000
判断热点代码需要热点探测功能,HotSpot VM所采用的的热点探测方式是基于计数器的热点探测
HotSpot VM为每一个方法建议两个不同类型的计数器,来实现热点探测,分别是方法调用计数器和回边计数器
方法调用计数器:统计方法的调用次数
回边计数器:统计循环体的循环次数
如果一个程序执行的时间够长,那么所有的方法几乎都可以成为热点代码,被JIT编译并缓存到方法区的CodeCache种,可以使用热度衰减来避免
如果不做任何设置,方法调用计数器统计的并不是方法被调用的绝对次数,而是一个相对的执行频流,即一段时间内方法被调用的次数,当超过一定的时间限度,如果方法的调用次数不足以让它提交给JIT,那这个方法的调用计数器就会减少一半,这个过程称为计数器的衰减,而这段是按称为此方法统计的半衰周期
进行热度衰减的动作是在进行GC时顺便进行的,热度衰减可以通过参数来关闭,让方法计数器统计方法调用的绝对次数,这样只要系统工作的够久,绝大部分方法都会成为热点代码,被JIT编译,前提是元空间的大小足够