JVM-执行引擎

139 阅读4分钟

执行引擎概述

JVM主要任务是负责装载字节码到其内部,但字节码并不能够直接运行在操作系统之上,因为字节码指令并非等价于本地机器指令。它内部包含的仅仅只是一些能够被JVM所识别的字节码指令,符号表以及其他辅助信息,那么如果想要让一个Java程序运行起来,执行引擎的任务就是将字节码指令解释/编译为对应平台上的本地机器指令才可以。简单来说,JVM的执行引擎充当了将高级语言翻译为机器语言的译者。

工作过程

  1. 执行引擎在执行的过程中,究竟需要执行什么样的字节码指令完全依赖于PC寄存器。
  2. 每当执行完一项指令操作后,PC寄存器就会更新下一条需要被执行的指令地址
  3. 当然方法在执行的过程中,执行引擎有可能会通过储存在局部变量表中的对象引用准确定位到储存在Java堆中的对象实例信息,以及通过对象头中的元数据指针定位到目标对象的类型信息。

Java代码编译和执行的过程

Java代码的编译是由Java源码编译器来完成,流程图如下所示:

graph LR
源代码 --> 词法分析 --> Token流 --> 语法分析器 --> 抽象语法树 --> 语义分析器 --> 注解抽象语法树 --> 字节码生成器 --> JVM字节码

Java字节码的执行是由JVM执行引擎来完成,流程图如下所示:

graph LR
JVM字节码 --> 机器无关优化 --> 中间代码 --> 机器相关优化 --> 中间代码 --> 寄存器分配器 --> 中间代码 --> 目标代码生成器 --> 目标代码
JVM字节码 --> 字节码解释器

解释器

当Java虚拟机启动时会根据预定义的规范,对字节码采用逐行解释的方式执行,将每条字节码文件中的内容翻译为对应平台的本地机器指令执行。

JIT编译器

就是虚拟机将源代码直接编译成和本地机器平台相关的机器语言。

二者关系

当虚拟机启动的时候解释器可以首先发挥作用而不必等待及时编译器全部编译完成在执行。这样可以省去许多不必要的编译时间,并随着程序运行时间的推移,即时编译器逐渐发挥作用,根据热点探测功能,将有价值的字节码编译为为本地机器指令,以换取更高的程序执行效率。

热点代码及探测方式

  • 一个被多次调用的方法,或者是一个方法体内部循环次数多的循环体都可以被称为“热点代码”,因此可以通过JIT编译器编译为本地机器指令,也称“栈上替换”。
  • 目前hotspot VM 采用的热点探测方式是基于计数器的热点探测。

计数器

采用基于计数器的热点探测,hotspot VM会为每一个方法都建立两个不同类型的计数器

方法调用计数器(用于统计方法的调用次数)

  • 这个计数器就用于统计方法被调用的次数,它的默认阈值在Client模式下是1500 次,在Server模式下是10000次。超过这个阈值,就会触发JIT编译。
qq_pic_merged_1658548117381.jpg
  • 热度衰减
  • 如果不做任何设置,方法调用计数器统计的并不是方法被调用的绝对次数,而是一个相对的执行频率,即一段时间之内方法被调用的次数。当超过一定时间限度,如果方法的调用次数仍然不足以提交给JIT编译器编译,那么这个方法计数器就会减少一半。

回边计数器(用于统计循环体执行的循环次数)

QQ20220723-0.jpg

JIT分类

在HotSpot VM中内嵌有两个JIT编译器,分别为Client CompilerServer Compiler,但绝大多数下称为C1编译器和C2编译器。可用命令显式指定Java虚拟机在运行时到底使用哪种编译器。

-client:使用c1编译器,会进行简单和可靠的优化,耗时短,以达更快的编译速度。

  • 方法内联:将引用的函数代码编译到引用点处,这样可用减少栈帧的生成,减少参数传递和跳转过程。
  • 去虚拟化:对唯一的实现类进行内联
  • 荣誉消除:在运行期间把一些不会执行的代码折叠掉

-server:使用c2编译器,进行耗时较长的优化,以及激进优化。但优化的代码执行效率高。

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