当前位置: 首页 > 后端技术 > Java

深度剖析JVM执行引擎

时间:2023-04-02 00:18:50 Java

程序与机器通信的桥梁1.闲聊相信很多朋友在出国旅游或者与外国朋友交流的时候都会遇到语言不通的情况。这时候就需要掌握相应的外语或者有翻译。作者只会中文,所以我需要一个翻译来和不懂中文的外国朋友交流。我们的执行引擎类似于这个“翻译器”。2.概述执行引擎的作用是将字节码指令解释或编译成相应平台上的本地机器指令。简单来说,执行引擎充当从高级语言到机器语言的翻译器。对于Hotspot虚拟机来说,执行引擎包含两部分:解释器和JIT编译器(即时编译器)。下图展示了执行引擎的原理:3.解释器解释器的作用是一个运行时翻译器,将字节码文件中的内容翻译成相应平台的本地机器码指令。一条字节码指令被解释执行后,再根据pc寄存器中记录的下一条需要执行的字节码指令进行解释操作。JVM解释器有两套,一套是古老的字节码解释器,一套是常用的模板解释器。1、字节码解释器字节码解释器在执行时通过纯软件代码模拟字节码执行,效率很低。2、模板解释器模板解释器将每个字节码与一个模板函数相关联,模板函数在这条字节码指令执行时直接生成机器码,从而提高了解释器的性能。在常用的HotSpotVM中,解释器主要由解释器模板和代码模块组成。解释器模板:实现了解释器的核心功能。code模块:用于管理HotSpotVM在运行时生成的native机器码指令。4、即时编译器(JITcompiler)即时编译器的目的是防止函数被解释执行,而是将整个函数体编译成机器码指令。每次执行函数时,只执行编译后的机器码。这种方法可以大大提高效率。1.热点代码及检测方法当然,是否需要JIT编译器将字节码直接编译成相应平台的机器码,取决于被调用代码的执行频率。需要被JIT编译器编译成机器码的字节码也称为热码。JIT编译器会对热点代码进行深度优化,将其从字节码编译成机器码,缓存在方法区,提高代码执行效率。JIT编译方式发生在方法执行的过程中,所以也被称为_on-stackreplacement_,简称OSR(OnStackReplacement)编译。通过热点检测的方法,判断某个方法被调用了多少次,或者循环体执行了多少次才达到阈值,然后进行编译。HotspotVM热点检测方法是基于计数器的。这种基于技术的热点检测方法分为两种:1.方法调用计数器2.边缘计数器用于统计方法调用的次数。其默认阈值在客户端模式下为1500次,在服务器模式下为10000次。超过此阈值会触发JIT编译。当然这个阈值也可以通过修改虚拟机参数-XX:CompileThreshold来手动指定。当一个方法被调用时,它会首先检查该方法是否已经被JIT编译过。如果存在,它将首先使用已编译的本机代码执行它。如果不存在,则会对该方法的调用计数器加一,然后判断计数器的值是否超过配置的阈值。如果超过,则向JIT编译器提交对该方法的编译请求。下面是方法调用计数器执行流程图:关于方法调用计数器,如果不做任何设置,方法调用计数器统计的不是方法被调用的绝对次数,而是一个相对的执行频率。当超过一定的时间限制,如果该方法的调用次数仍未达到阈值,则该方法的调用计数器将减半。这个过程称为方法调用计数器的热衰减,这段时间称为方法。半生命周期。热衰的过程是虚拟机进行垃圾回收时顺便完成的,只是费力的事情。可以使用虚拟机参数-XX:-UseCounterDecay关闭热衰减。在这种情况下,只要运行时间足够长,大部分方法都会被编译成本地代码。最后,您还可以使用-XX:CounterHalfLifeTime参数以秒为单位设置半生命周期的时间。1.2边界计数器其作用是统计方法中循环体代码的执行次数。遇到字节码中的控制流向后,跳转指令称为“向后”。显然,建立边缘计数器统计的目的是为了触发OSR编译。下面是后端计数器执行的流程图:关于OSR编译,上面提到了2.即时编译器归类在HotspotVM中,里面嵌入了两个JIT编译器,即client编译器和服务器编译器,但大多数情况下我们简称为C1编译器和C2编译器。可以通过命令显示指定JVM在运行时使用的是哪个JIT编译器。2.1c1编译器指定Java虚拟机运行在客户端模式,使用C1编译器。C1编译器在很短的时间内对字节码执行简单而可靠的优化。为了达到更快的编译速度,但是编译后的代码执行速度比较慢。C1编译器主要包括方法内联、去虚拟化和冗余消除。方法内联:将引用的函数代码编译到引用点,可以减少栈帧的生成、参数传递和跳转过程。去虚拟化:仅内联已实现的类。冗余消除:重叠一些在运行时不会执行的代码。2.2c2编译器指定Java虚拟机运行在server模式,使用C2编译器。C2编译器优化代码的时间比较长,编译时间也长。但是编译后的代码执行得更快。C2的优化主要在全局层面,是逃逸分析优化的基础。基于逃逸分析,C2有以下优化:标量替换:用标量值替换聚合对象的属性值。在栈上分配:对于没有逃逸的对象,分配在栈上,而不是堆上。synchronizationelimination:清除同步操作,通常简称synchronized。2.3Graal编译器从JDK10开始,在C1编译器和C2编译器之后,HotSpotVM增加了Graal即时编译器。短短几年编译效果就赶上了C2编译器。目前,带有“ExperimentalStatus”标签,需要使用开关参数-XX:+UnlockExperimentalVMOptions、-XX:+UseJVMCICompiler激活此编译器后才能使用。5、解释器与JIT共存需要解释器与JIT共存的原因有以下几点:程序启动时,解释器可以立即生效,节省编译时间。如果编译器要执行,需要将字节码编译成本地机器码,并将编译后的机器码缓存起来。编译需要一定的时间。编译后的本地机器码执行效率高。因此,在两种并存的模式下,解释器先发挥作用,而不必等到即时编译器完全编译执行完毕,可以节省不必要的编译时间。随着程序继续运行,编译器发挥作用。根据热点检测功能,越来越多的字节码被编译成本地机器码,以获得更高的执行效率。6、执行引擎执行程序的方式HotSpotVM默认采用解释器和JIT编译器共存的架构。最后完全由解释器执行,或者完全由即时编译器执行。-Xint:完全以解释器模式执行程序-XComp:完全以即时编译器模式执行程序。如果即时编译器出现问题,解释器会介入执行;-Xmixed:使用解释器+即时编译器的混合模式共同执行程序,HotStopVM默认为该模式。7、参考源码编程文档:https://gitee.com/cicadasmile/butte-java-note应用仓库:https://gitee.com/cicadasmile/butte-flyer-parent