JVM09-执行引擎

1 执行引擎

1.1 执行引擎概述

执行引擎是JVM核心组成之一,由于操作系统只能识别机器指令,想要在机器上执行程序,不管什么语言最终都需要转换成机器指令,JVM中的执行引擎主要将字节码指令转换为机器指令并执行
在这里插入图片描述
执行引擎的大致工作过程:

1,执行引擎执行的过程中根据程序计数器中存储的
指令地址来执行对应的指令

2,每当执行引擎执行完一条指令
程序计数器就会更新下一条需要被执行的指令地址

3,当方法执行的过程中,执行引擎可能会通过
存储在局部变量表中的引用变量访问堆中对应的对象
以及通过对象头中的类型指针找到元空间中对应的类型信息

在这里插入图片描述

1.2 Java代码的编译器

Java代码的编译器有两种,一种是前端编译器,一种是后端编译器

前端编译器,如javac,即将java文件编译成class文件
在这里插入图片描述
后端编译器,即将class文件中的字节码指令编译成本地机器指令:
在这里插入图片描述

解释器:
当JVM启动时会根据预定义的规范对字节码采用逐行解释的方法执行
将每条字节码指令编译成对应的本地机器指令并执行

JIT编译器(即时编译器):
将字节码指令直接编译成本地机器指令,只负责热点代码的编译工作

在这里插入图片描述

默认情况下,HotSpot VM采用解释器与JIT编译器并存的架构,开发者可以通过参数设置完全使用解释器,还是完全使用JIT编译器

-Xint 完全使用解释器来执行程序

-Xcomp 完全使用JIT编译器 如果编译出错 解释器会介入执行

-Xmixed 解释器+JIT编译器共同执行程序(默认)

1.3 解释器

解释器实质上是一个运行时翻译官,它会将字节码指令编译成本地机器指令并执行,当一条字节码指令被解释执行完成后,接着再从程序计数器中执行编译执行下一条字节码指令

在Java发展的历程里,一共有两套解释执行器,古老的字节码解释器,和现在普遍使用的模板解释器

字节码解释器:
通过纯软件代码模拟字节码的执行,效率非常低

模板解释器:
每一条字节码和一个模板函数相关联
模板函数能直接生产这条字节码执行时的机器码

在HotSpot VM中,解释器主要由Interpreter模块和Code模块构成

Interpreter模块: 实现解释器的核心功能

Code模块: 管理HotSpot VM运行时生成的本地机器指令

由于解释器在设计和实现上非常简单,除Java外,Python,Perl,Ruby等也是基于解释器执行的,基于解释器执行已经沦落为低效的代名词,为了解决这个问题,JVM提供了即时编译技术

1.4 JIT编译器

即时编译的目的是避免方法被解释执行,将整个方法编译成机器码,每次执行该方法时,只执行经过即时编译后的机器码,这种方式可以使执行效率大幅度提升

虽然解释器效率较低,JIT编译器效率较高,但是这二者在一起工作比起单纯使用JIT编译器的效果更好

因为当程序启动后,解释器可以立刻执行,响应速度快
而JIT编译器需要将热点代码编译成机器指令,判断热点代码需要一定的时间

尽管JRockit VM中没有解释器,字节码全部靠JIT编译器编译后执行
但为此程序的启动一定需要更多的时间,对于服务端应用而言启动时间不是重点
而对于看重启动时间的应用场景中,不能全靠JIT编译完后再执行,启动会很慢

为此,当JVM启动后,解释器可以立刻发挥作用,随着时间的推移,JIT根据热点探测功能,将有价值的字节码编译成机器指令,获得更高的执行效率

1.4.1 HotSpot VM中JIT的分类

JDK10之前在HotSpot VM中内嵌两个JIT编译器,分别为Client Compiler(C1编译器)和Server Compiler(C2编译器),开发者可以通过命令来设置JVM运行时使用哪种JIT,但64位机器只能使用Server Compiler

-client 指定使用C1编译器
C1编译器会对字节码进行简单和可靠的优化,耗时短
以达到更快的编译速度

-server 指定使用C2编译器
C2编译器会进行更长时间的优化,以及采取更激进的优化策略,耗时长
使得编译后的代码执行效率更高

C1编译器的优化策略:

1,方法内联
将方法中调用的方法编译到一起,减少栈帧的生成,参数的传递和跳转

2,去虚拟化
将唯一的实现类直接编译到一起

3,冗余消除
运行时把一些不会执行的代码折叠掉

C2编译器的优化策略:

1,标量替换
用标量替代聚合对象的属性值

2,栈上分配
对于未逃逸的对象不在堆中而在栈上分配(通过标量替换 而不是创建对象)

3,同步消除
清除无效同步操作,synchronized块和关键字

在JDK10之后,HotSpot VM又内置了新的JIT编译器,Graal编译器

1.4.2 热点代码

当某段字节码指令是否需要被JIT编译器编译成机器指令,是需要根据这段代码被调用执行的频率决定,高频被调用的代码也称为热点代码,JIT编译器在运行期间会对热点代码做出深度优化并将其编译成机器指令缓存到方法区的Code Cache,以提升Java程序的执行性能

热点代码:
一个被多次调用的方法,或者一个方法体内部循环较多的循环体
都可以称为热点代码,通过JIT编译成机器指令

一个方法需要被执行多少次,或一个循环体要执行多少次循环才能算热点代码,这必然需要一个明确的阈值,这个阈值在C1中是1500,在C2中是10000

判断热点代码需要热点探测功能,HotSpot VM所采用的的热点探测方式是基于计数器的热点探测

1.4.3 热点探测

HotSpot VM为每一个方法建议两个不同类型的计数器,来实现热点探测,分别是方法调用计数器和回边计数器

方法调用计数器:统计方法的调用次数
在这里插入图片描述

回边计数器:统计循环体的循环次数
在这里插入图片描述

1.4.4 热度衰减

如果一个程序执行的时间够长,那么所有的方法几乎都可以成为热点代码,被JIT编译并缓存到方法区的CodeCache种,可以使用热度衰减来避免

如果不做任何设置,方法调用计数器统计的并不是方法被调用的绝对次数,而是一个相对的执行频流,即一段时间内方法被调用的次数,当超过一定的时间限度,如果方法的调用次数不足以让它提交给JIT,那这个方法的调用计数器就会减少一半,这个过程称为计数器的衰减,而这段是按称为此方法统计的半衰周期

进行热度衰减的动作是在进行GC时顺便进行的,热度衰减可以通过参数来关闭,让方法计数器统计方法调用的绝对次数,这样只要系统工作的够久,绝大部分方法都会成为热点代码,被JIT编译,前提是元空间的大小足够

1.5 字节码指令的执行过程

在这里插入图片描述
1,执行引擎会先检查当前字节码指令是否已经被JIT编译过,如果被编译过,则从方法区的CodeCache中找到对应的机器指令,直接执行机器指令

2,如果没有被编译过,调整该指令所属的方法调用计数器或回边计数器,再判断计数器的值是否超过了阈值

3,如果超过了阈值,则向JIT提交一个编译请求,JIT将编译当前字节码指令,并将编译后的结果,即字节码指令对应的机器指令存放在方法区的CodeCache中此时执行引擎直接执行编译后的机器指令

4,如果计数器的值没有超过阈值,那么由解释器来编译执行

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值