4、执行引擎

1. 执行引擎介绍

执行引擎是java虚拟机和核心组件之一。

JVM的主要任务是负责装载字节码到内部,但字节码不能直接运行在操作系统上,因为字节码不等同于机器指令,它内部包含的仅仅只是一些能被JVM识别的字节码指令、符号集,以及其他辅助信息。
执行引擎(Execution Engine)的任务就是将字节码指令解释/编译为对应平台上的本地机器指令才可以执行。简单说,JVM中的执行引擎充当了将高级语言翻译成机器语言的翻译者。

执行引擎支持的解释或编译功能。首选,java源码被编译为字节码文件,注意此地java源码的编译不是执行引擎中编译的功能,java源码的编译属于前端编译功能,比如idea、eclipse自带的前端编译功能,把java源码编译成字节码文件,编译成字节码文件需经过如下图步骤。而执行引擎中的编译与解释是对前端编译器编译成的字节码文件进行解释或者编译。执行引擎中的编译功能也可以被叫做后端编译。
在这里插入图片描述
而执行引擎中的编译或者解释主要经过以下步骤,其中程序源码指的是字节码文件中的源码
在这里插入图片描述
其中解释器为:当java虚拟机启动时会根据定义的规范对字节码采用逐行解释的方式执行,将每条字节码文件的内容翻译为对应平台的本地机器执行执行。
JIT(just in time comiler)编译器:就是虚拟机字节码直接编译成和本地机器平台相关的机器指令。

java属于半解释半编译型语言

2. 指令理解

机器指令:可以直接被CPU读取执行的,是二进制指令,可以直接被机器识别的。
指令:由于机器指令是有0和1组成的二进制序列,可读性太差,于是发明了指令,指令就是把机器指令中特定的0和1序列简化成对应的指令(一般为英文简写,如mov、inc等),可读性好。不同硬件平台执行的指令可能不同。
指令集:不同硬件平台支持的指令是有差异的,因此每个平台所支持的指令,称为对应平台的指令集。如常见的X86指令集,对应的是x86架构平台;ARM指令集,对应的是ARM架构平台。
汇编语言:指令可读性还是太差,于是发明了汇编语言。汇编语言用助记符(Mnenonics)代替指令操作码,用地址符号代替指令或操作数地址。不同的硬件平台,汇编语言对应不同的机器指令集,由于计算机只认识机器指令,所以汇编语言编写的程序还必须翻译成机器指令。

如下图所示,对于汇编语言,需要翻译成机器指令才可以被CPU执行,而高级语言,要先被翻译成汇编语言,然后再由汇编语言翻译成机器语言才可被CPU执行。
但对于java语言,经前端编译器编译成字节码文件,字节码是一种二进制代码,需要解释器直接翻译成机器指令进行执行。
在这里插入图片描述

3. Hotspot虚拟机中解释器为什么与JIT编译器并存

解释器:是一个运行时“翻译者”,将字节码中的内容翻译为对应平台的本地机器执行执行。当一条字节码指令被解释执行完成后,然后再根据PC寄存器中记录的下一条需要被执行的字节码执行执行解释操作。
JIT编译器:为了提高执行效率,使用JIT方式将字节码编译成机器码后再执行。JRockit编译器内部只是使用了JIT方式,而HotSpot内部是解释器和JIT编译器同时并存。

为什么JIT编译器效率高,HotSpot内存中还要保留解释器模式?

当java虚拟机启动时,解释器可以首先发挥作用,而不必等待即时编译器全部编译完成后再执行,这样可以省去许多不必要的编译时间,但随着时间的推移,编译器发挥作用,越来越多的代码被编译成本地代码,执行效率反而变的更快。
HotSpot采用解释器和JIT并存架构,两者可以相互协助,各自取长补短,选择合适的方法来编译代码或者解释代码,目前,java程序运行性能已经达到了C/C++的地步。

阿里案例
机器在热机状态可以承受的负载要大于冷机状态,如果以热机状态时的流量进行切换流量,可能使处于冷机状态的服务器因无法承载流量而假死。在生产环境发布过程中,以分批的方式进行发布,根据机器数量划分成多个批次,每个批次的机器数至多占到整个集群的1/8,例如正在发布第一批时,第一批次的机器全部处于冷机状态,但剩下尚未发布的机器都还是之前未发布时的热机状态,能都加快执行效率,当发布第二批次时,第二批次的机器处于冷机状态,先前发布的第一批次的机器可能已经转为热机状态了。曾经有这样的案例,当程序员在平台分批次发布时,输入了分2批次发布,当发布第一批次时,还没有进行热点代码统计和JIT动态编译,导致有一半的机器处于冷机状态,当流量洪峰到来时,容易导致第一批次的机器全部宕机。

4. JIT热点代码探测方式

是否启用JIT编译器将字节码编译成机器指令,则要根据代码被执行的频率而定。那些执行频率高,需要被编译成机器码的代码称为热点代码,JIT会运行时根据那些频繁被调用的热点代码做出深度优化,将其直接编译成机器指令。

一个被多次调用的方法或者是一个方法内部循环次数较多的循环都可以被称为热点代码,这样的代码就可以通过JIT编译器编译成本地机器指令。一个方法被调用多少次或者一个循环体被循环多少次才可触发JIT编译需要一个明确的阈值。方法被调用次数或者方法内部循环次数达到阈值时,JIT编译器就将代码编译为本地机器指令,这种功能即为JIT的热点探测功能。
目前HotSpot采用的热点探测方式是基于计数器的热点探测方式。HotSpot会为每一个方法都创建2个不同类型的计数器,分别为方法调用计数器(Invocation Counter)和回边计数器(Back Edge Counter)。方法调用计数器用于统计方法的调用次数。回边计数器(在字节码中遇到跳转的指令称为回边)则用于统计循环体执行的循环次数。
阈值在Client模式下默认为1500次,在Server模式下默认为10000次,超过这个阈值就会触发JIT编译。当然阈值也可以通过虚拟机参数-XX:CompileThreshold来设定。
执行过程:当一个方法被调用时,首先检查方法区中方法是否被JIT编译过的版本,如果存在,优先使用本地编译过的代码来执行;如果不存在,则将此方法的调用计数器加1,然后判断方法调用计数器与回边计数器之和是否超过方法调用计数器的阈值,如果已超过,将会向JIT提交方法的编译请求,如果没超过进行解释执行代码。

执行流程图如下所示:
在这里插入图片描述

热度衰减
如果不做任何设置,方法调用计数器统计的并不是方法被调用的次数,而是一个相对执行的频率,即一段时间内方法被调用的次数,当超过一定的时间限度,如果方法的调用次数仍然达不到阈值,那么这个方法的调用计数器就会被减少一半,这个过程称为方法调用计数器热度的衰减(Counter Decay),而这段时间就称为此方法统计的半衰周期(Counter Half Life Time)。
执行热度衰减的动作是在虚拟机进行垃圾回收时顺便进行的,可以使用-XX:-UseCounterDecay来关闭热度衰减,让方法调用计数器统计方法被调用的绝对次数,这样只要系统运行足够长,绝大部分代码都会被编译成机器指令。另外也可以通过-XX:CounterHalfLifeTime参数这是半衰周期,单位是秒。

5. HotSpot中解释器、编译器设置方式

默认情况下,HotSpot虚拟机采用的是解释器与JIT并存的架构,当然根据具体场景,可以设置解释模式,也可以设置完全编译模式。设置参数如下:

  • -Xint: 完全采用解释器模式执行程序;
  • -Xcomp: 完全采用JIT编译模式执行程序,如果及时编译器出问题了,解释器会介入;
  • -Xmixed: 采用解释器与JIT编译并存方式。

示例如下
在这里插入图片描述


HotSpot内置了两种类型的JIT编译器,分别为Client Compiler和Server Compiler,也即C1和C2编译器,开发人员可以通过如下命令显示指定java虚拟机使用哪一种编译器,如下所示:

c1:指定java虚拟机在client模式下运行,c1编译器会对代码进行简单和可靠的优化,耗时短,编译速度更快;
c2:指定java虚拟机运行在server模式下,c2编译器对代码进行耗时较长的优化,以及激进优化,但优化后的代码执行效率更高。一般64位机器固定为c2编译器。

c1和c2编译器不同优化策略不同,c1优化器策略如下:

方法内联:将引用的函数代码编译到引用点处,这样可以减少栈帧的生成,减少参数传递以及跳转过程;
去虚拟化:对唯一的实现类进行内联,比如一个接口被多个类实现,通过接口调用时,在运行时才知道具体哪个实现类执行的,而如果接口只有一个实现类,在编译期就内联,就确定了执行类。
冗余消除:在运行期间把一些不会执行的代码折叠掉。

c2编译期主要是在全局层面,逃逸分析是优化的基础,基于逃逸分析(逃逸分析参考文章)在c2上有如下几种优化:

标量替换:用标量值代替聚合对象的属性值;
栈上分配:对于未逃逸的对象分配在栈而不是堆;
同步消除:消除同步操作,通常指synchronized

c2编译器启动时长比c1编译器慢,冷机情况下,c1花费时间短,但系统稳定运行后,即热机状态下,c2编译器执行速度远远快于c1编译器。


一般64位机器,jkd7之后,HotSpot虚拟机自动为server模式(c2模式),servier模式下会自动开启分层编译策略,由c1编译器和c2编译器相互协作共同来执行编译任务。c1编译器将字节码翻译成机器码,进行简单优化,c2编译器进行激进优化。

6. AOT编译器

JDK9引入了AOT(Ahead Of Time Compiler)编译器,即静态提前编译器,它借助Graal编译器,将所有输入的java类文件转换为机器码(注意是java源文件,而不是字节码了),并存放到动态共享库中。
即时编译器与静态编译器区别:即时编译器在程序运行过程中,将字节码转换为可在硬件上直接运行的机器码,而静态编译器是在程序运行之前,将.java源文件转换为机器码的过程。


AOT的优缺点
缺点:

  • 1.破坏了java一次编译,到处运行,要对不同的硬件和系统编译不同的发行包;
  • 2.降低了java链接过程的动态性,加载的代码在编译期必须已知;

优点:

  • 对于已经编译成的二进制库,可以直接运行,无须等待即时编译器编译,减少了java应用在冷机状态下运行慢问题。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值