乐趣区

关于jvm虚拟机:深入分析JVM执行引擎

程序和机器沟通的桥梁

一、闲聊

置信很多敌人在出国游览,或者与外国友人沟通的过程中,都会遇到语言不通的懊恼。这时候咱们就须要把握对应的外语或者领有一部翻译机。而笔者只会中文,所以须要借助一部翻译器能力与不懂中文的外国友人交换。咱们的执行引擎就相似于这部“翻译机”。

二、概述

执行引擎的作用就是将字节码指令解释或者编译为对应平台上的本地机器指令。简略来说,执行引擎充当了将高级语言翻译为机器语言的翻译者。对于 Hotspot 虚拟机,执行引擎中蕴含两局部:解释器和 JIT 编译器(即时编译器)。下图是执行引擎的原理:

三、解释器

解释器所承当的角色就是一个运行时 翻译者 ,将字节码文件中的内容 翻译 为对应平台的本地机器码指令。当一条字节码指令被解释执行后,接着再依据 pc 寄存器中记录的下一条须要被执行的字节码指令执行解释操作。JVM 解释器一共有两套,一套是远古的 字节码解释器 ,另一套是当初广泛应用的 模板解释器

1、字节码解释器

字节码解释器在执行过程中通过 纯软件代码 模仿字节码执行,效率非常低。

2、模板解释器

模板解释器将 每一条字节码和一个模板函数关联,模板函数中间接产生这条字节码指令执行时的机器码,从而进步了解释器的性能。在罕用的 HotSpot VM 中,解释器次要由 Interpreter 模板和 code 模块形成。Interpreter 模板:实现了解释器的外围性能。code 模块:用于治理 HotSpot VM 在运行时生成的本地机器码指令。

四、即时编译器(JIT 编译器)

即时编译器的目标是防止函数被解释执行,而是将整个函数体编译成机器码指令,每次函数执行时,只执行编译后的机器码即可,这种形式能够大大的提高效率。

1、热点代码及探测形式

当然,是否须要 JIT 编译器将字节码间接编译成对应平台的机器码,须要依据代码被调用的 执行频率 而定。须要被 JIT 编译器编译成机器码的字节码,也称为 热点代码 ,JIT 编译器会对热点代码做出 深度优化 ,将其从字节码编译成机器码, 并缓存到办法区 ,进步代码的执行效率。
JIT 编译的形式产生在办法执行过程中,因而也被称之为_栈上替换_,或简称 OSR(On Stack Replacement) 编译。通过 热点探测 的办法,判断一个办法被调用多少次,或循环体执行多少次才能够达到阈值,进行编译。而 Hotspot VM 热点探测的形式是基于计数器实现的。这种基于技术的热点探测形式又分为两种:1. 办法调用计数器 2. 回边计数器

对于栈上替换这里笔者不开展赘述,有趣味的小伙伴能够自行理解下

1.1 办法调用计数器

办法调用计数器用于统计办法调用次数,它的默认阈值是 client 模式下是 1500 次,在 server 模式下是 10000 次。超过这个阈值,就会触发 JIT 编译。当然,这个阈值也能够通过批改虚拟机参数 -XX:CompileThreshold 来手动指定。
当一个办法被调用的时候,会优先查看该办法是否被 JIT 编译过,如果存在,则优先应用编译过的本地代码来执行,如果不存在,则将此办法的调用计数器加一,而后再判断计数器的值是否超过配置的阈值。如果曾经超过了,就会向 JIT 编译器提交一个该办法的编译申请。上面是办法调用计数器执行的流程图:

对于办法调用计数器,如果不做任何设置,办法调用计数器统计的并不是办法被调用的相对次数,而是一个绝对执行的频率。当超过肯定的工夫限度,如果办法的调用次数依然达不到阈值,那这个办法的调用计数器就会被缩小一半,这个过程称为办法调用计数器的 热度衰减 ,而这段时间被称作为该办法的 半衰周期
进行热度衰减的过程是虚拟机进行垃圾回收的时候顺便进行的,举手之劳而已。能够应用虚拟机参数 -XX:-UseCounterDecay 来敞开热度衰减。这样的话,只有运行工夫足够长,绝大部分办法都会被编译成本地代码。最初,还能够应用 -XX:CounterHalfLifeTime 参数设置半衰周期的工夫,单位为秒。

1.2 回边计数器

它的作用是统计一个办法中 循环体代码执行次数,在字节码中遇到管制流向后,跳转的指令称为“回边”。显然,建设回边计数器统计的目标是为了触发 OSR 编译。上面是回边计数器执行的流程图:

对于 OSR 编译上文中有提到

2、即时编译器分类

在 Hotspot VM 中,内嵌有两个 JIT 编译器,别离为 client compiler 和 server compiler,然而大多数状况下咱们简称 C1 编译器和 C2 编译器。能够通过命令显示的指定 JVM 在运行时到底应用哪种 JIT 编译器。

2.1 c1 编译器

指定 Java 虚拟机运行在 client 模式下,应用 C1 编译器。C1 编译器会对字节码进行简略和牢靠的优化,耗时短。以达到更快的编译速度,然而编译后的代码执行速度绝对慢。C1 编译器次要有办法内联,去虚拟化,冗余打消。

  1. 办法内联:将援用的函数代码编译到援用点处,这样能够缩小栈帧的生成,缩小参数传递以及跳转过程。
  2. 去虚拟化:对惟一实现的类进行内联。
  3. 冗余打消:在运行期间把一些不会执行的代码叠掉。

2.2 c2 编译器

指定 Java 虚拟机运行在 server 模式下,应用 C2 编译器。C2 编译器对代码优化工夫长,编译工夫也长。然而编译后的代码执行速度比拟快。C2 的优化次要在全局层面,逃逸剖析式优化的根底。基于逃逸剖析,C2 上有如下几种优化:

  1. 标量替换:用标量值代替聚合对象的属性值。
  2. 栈上调配:对于未逃逸的对象调配在栈上而不是堆上。
  3. 同步打消:分明同步操作,通常指 synchronized。

2.3 Graal 编译器

JDK10 起,在 C1 编译器和 C2 编译器之后,HotSpot VM 新增了一个 Graal 即时编译器。编译成果短短几年的工夫就追平了 C2 编译器。目前,带着“试验状态”标签,须要应用开关参数 -XX:+UnlockExperimentalVMOptions-XX:+UseJVMCICompiler 去激活这个编译器,能力应用。

五、解释器和 JIT 并存

为什么须要解释器和 JIT 并存,起因有几点:

  1. 当程序启动的时候,解释器能够马上发挥作用,省去编译的工夫。
  2. 编译器想要执行,须要把字节码编译成本地机器码,并且缓存编译后的机器码,编译须要肯定的工夫。
  3. 编译后的本地机器码,执行效率高。所以,在两种并存的模式下,解释器首先发挥作用,而不用等到即时编译器全副编译完在执行,这样能够省去不必要的编译工夫。
  4. 随着程序持续一直运行,编译器发挥作用,依据 热点探测 性能,把越来越多的字节码编译成本地机器码,取得更高的执行效率。

六、执行引擎执行程序的形式

在默认的状况下,HotSpot VM 采纳的是解释器和 JIT 编译器并存的架构,当然读者能够依据具体的利用场景,通过虚拟机参数,为虚拟机指定在运行时到底是齐全采纳解释器执行,还是齐全采纳即时编译器执行。

  1. -Xint:齐全采纳解释器模式执行程序
  2. -XComp:齐全采纳即时编译器模式执行程序。如果即时编译器呈现问题,解释器会染指执行;
  3. -Xmixed:采纳解释器 + 即时编译器的混合模式独特执行程序,HotStop VM 默认就是这个模式。

七、参考源码

编程文档:https://gitee.com/cicadasmile/butte-java-note

利用仓库:https://gitee.com/cicadasmile/butte-flyer-parent
退出移动版