JVM之执行引擎

本文深入探讨JVM执行引擎的工作原理,包括字节码解释器和即时编译器(JIT)的角色,以及它们如何协作提高Java应用程序的性能。介绍了热点代码探测、分层编译策略和Graal编译器的最新进展。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

返回主博客

 

概述

执行引擎充当字节码的翻译者,与操作系统打交道,并执行代码。

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

 

两条途径

执行引擎执行字节码指令可以用两条途径

1、使用解释器(interpreter)对字节码进行解释执行、

2、使用即时编译器(JIT Compiler),将字节码编译成机器指令后再执行。

起初在Sun Classic VM中只使用解释器,后来在HOTSPOT之后二者结合使用。如下图

 

java代码编译和执行的过程

前端编译器:图中黄色部分就是前端编译器所作事情,将java编译成class

JIT编译器:图中蓝色部分。

解释器:图中绿色部分。

AOP编译器:黄色+绿色部分,直接将java文件编译为机器指令。

 

解释器

承担运行时的翻译者,将class文件翻译成对应平台的机器指令执行。当一条字节码指令被解释执行完成之后,接着再根据PC寄存器的下一条直接进行解释操作。

为什么要有class文件,JVM为什么不直接解释java文件?

  • 1、java执行中依赖的指令集架构是基于栈的指令集架构。程序在运行时是依赖对于栈的操作的。就好比我们在科学计算器中输入一串式子:(((1+2)*(2+3)+4*5)*3让计算器计算。计算器需要对这串的每个操作和数字进行分析,遇到优先级低的操作符时需要将数字入栈,遇到操作符优先级高的则取栈顶元素计算,然后计算结果入栈。java的基于栈的指令集架构就类似于科学计算器算法。就是出栈运算,结果入栈。但是对于这串式子的解析和判断优先级是要耗时的,所以我们可以直接将其编译,生成那些对栈的操作指令,这样在实际运行的时候便快一点。java也是如此,直接将java文件交给JVM运行,需要对其进行语法词法分析,这是相当耗时的。
  • 2、java不仅是跨平台的语言,并且JVM是一个跨语言的平台,其他自开发语言都可以编译成class文件交给JVM运行。比如Scala、Kotlin、Groovy、Jython。
     

两套解释器

java的历史进程中有两套解释器,古老的字节码解释器,和现在普遍使用的模板解释器。

  • 1、字节码解释器:

通过纯软件代码模拟字节码的执行,就是读到字节码时,先翻译这行字节码的意思,比如 “iload_1”,那么就是纯执行C语言代码,operationStack.push(local[1])。

  • 2、模板解释器:

将字节码和一个模板函数相关联,比如 “iload_1", 这个iload和一个映射到了一个函数指针,直接就对应运行上了。这个模板函数就直接对应了其字节码执行需要的机器指令。

如下图,就是Jpython的字节码解释用到的模板,器中Load_FAST,Load_CONST等就是用是C语言实现的模板,其映射了一类字节码指令。

 

在hotspot虚拟机中,解释器主要由Interpreter模块和Code模块组成。

  1. Interpreter实现解释器核心功能。
  2. Code模块用于管理HotSpot在运行时生成的本地机器指令。

 

JIT编译器

对字节码逐行解释执行时很慢的,为了解决这个问题,JVM支持即时编译的技术,可以将整个函数编译为机器码,函数执行时,每次执行机器码即可。

何时选择JIT编译器?热点代码及其探测方式

根据代码的执行频率而定,将频率高的编译为本地本地代码,这部分代码称为热点代码。一个频繁被执行的方法或循环次数较多的循环体,可以被称为热点代码。将这部分代码编译为机器指令,这个过程称为“栈上替换”或OSR(On Stack Replacement)。目前HotSpot的热点探测方式为基于计数器的热点探测。JVM为每个方法都建立两个不同类型的计数器。(我们操作系统好像使用的时换页算法

  • 方法调用计数器:统计方法调用次数
  • 回边计数器:统计循环体循环次数

计数器的默认阈值在clent模式下是1500,在service模式是10000,可以用XX:CompileThreshold控制。

如果不加任何设置,这个统计计数值,是会衰减的,当超过一定的时间限度(半衰期),该方法或者循环体不能到达阈值,会衰减至原来一半,也就是说我们这个时间计数是统计在某一限定范围的被调用次数。(个人感觉这个不合理,既然你已经认为他不好达到阈值,为什么还要统计,统计完还得给他衰减一半,假如它有被调用还得count++)

  • 可以用-XX:UseCounterDecay=false关闭衰减。
  • 可以用-XX:CounterHalfFifeTime 设置半衰期。

方法计数器的控制大致过程如下图:

回边计数器的控制过程如下图

注意

  • 1、图中的codeCache存储在方法区的元空间。
  • 2、回边计数器值是与方法计数器值求和生效的。

C1和C2

hotSpot内嵌两个JIT编译器:client Compiler 和 server Compiler,简称C1和C2。可以通过一下参数设置

-client:对字节码简单可靠的优化,耗时短,64位操作系统下配置-client会被忽略。

-server:对字节码执行更加耗时和激进的优化,但执行效率更高,默认64为操作系统下为server,即使配置-client也会被忽略。

C1和C2的不同优化策略

C1:

  • 方法内敛:将引用的函数代码编译到引用点处,这样可以减少栈帧的生成,减少参数传递以及跳转过程。
  • 去虚拟化:对唯一的实现类进行内联
  • 冗余消除:将运行期间不会执行和逻辑上无效的代码折叠。

C2:优化主要在全局层面,逃逸分析是优化的基础。基于逃逸分析有以下优化。(C++编写)

  • 标量分离
  • 同步消除
  • 栈上分配

分层编译策略

程序解释运行,如果不开启性能监控,可以触发C1编译,可以简单优化。如果开启性能监控,C2会根据性能监控信息进行激进优化。如果激进优化失败,会退回到解释执行。

在jdk1.7之后,如果指定-server,默认会开启分层编译策略,C1和C2相互协作共同执行编译任务。

总结

  • 一般来说,JIT编译出来的机器码性能比解释器高。
  • C2编译器启动时长比C1慢,系统稳定执行之后,C2编译器的执行速度远远快于C1编译器

 

解释器和JIT编译器

解释器和JIT编译器需要配合使用才可可以在普遍场景下高效。因为编译需要时间,如果要等编译完在执行,就像纸上谈兵,只谈规划不执行,规划好久之后才执行,后面执行很快,但是启动速度。使用解释器就好像不做规划就冲动执行,但是在执行过程中速度很慢。二者结合才可高效。如下图,二者就像执行引擎的两条腿。(就像我们写代码一样不能只干想等想好再写,也不能想都不想,写到哪里算哪里)

 

Jrockit虚拟机,没有解释器,他适合服务器端的应用,但是对于其他场景,比如前端应用,我们即想启动速度快又想运行快,就得二者结合。

可以配置

  • 只使用解释器 -Xinit
  • 只使用编译器 -Xcomp
  • 混合使用 -Xmixed

 

热机和冷机

解释执行和编译执行在线上环节有着微妙的辩证关系:机器在热机状态下能承受的负载要大于冷机状态,如果以热机状态的流量进行切流可能导致处于冷机状态的服务无法承受负载而假死。

案例:

在生产中,以分批的方式进行发布,根据机器数量分批次发布。每个批次的数量占集群的1/8。曾经某程序员在发布时将批次误填为2。在热机状态下,下一半机器勉强可以承受流量,但是由于刚刚启动状态的机器属于解释执行阶段,还没有热点代码统计和JIT编译,导致刚刚启动的机器全部宕机    -------阿里团队

 

java慢?  

因为java要在运行过程中解释或编译。class文件的执行要依赖JVM。而exe自己本身就是一个可以直接执行的文件。

C/C++的编译包括编译和汇编,链接之后就生成了一个二进制的exe可以执行文件。exe文件是一种可在操作系统存储空间中浮动定位的可执行程序,直接和操作系统打交道,执行速度很快。而java还需要再执行过程中再次解释或者编译,并且必须要在二者之间达到一种平衡。如下图为C/C++的执行过程。

写在最后

Graal编译器

自JDK10起,HotSpot又引入了一个全新的即时编译器:Graal编译器。

编译效果短短几年追评超过C2编译器,未来可期。

目前”实验状态",需要使用开关参数

使用-XX:+UnlockExperimentalVMOptions -XX:+UserJVMCICompiler激活。

 

AOT编译器

jdk9引入AOT编译器,静态提前编译(Ahead of Time Compiler)

java9引入实验性AOT编译工具jaotc。它借助Graal编译器,将java文件转化位机器码,并存放生成的动态共享库之中。

所谓AOT编译器,是与即时编译器相互对立的一个概念。我们知道,即时编译器指的是在程序的运行过程中,将字节码转化位可在硬件上直接运行的机器码,并部署至托管环境中的过程。而AOT编译指的是,在程序运行之前,将字节码转化位机器码的过程。

使用过程:

.java --使用javac--> .class --使用jaotc --> .so 然后将so文件放到动态共享库之中。

其好处是:java虚拟机加载已经预编译成二进制库,可以直接执行,不必等待即时编译器的预热,减少带来第一次运行慢的不良体验。

缺点:打破”一次编译,到处运行“,必须位每个不同硬件、OS编译对应的发行包。

降低了java的链接过程的动态性,加载的代码在编译器必须全部已知。

还需要继续优化,目前只支持Linux x64 java base

 

 

 

 

 

 

<think>嗯,用户问的是JVM执行引擎包括什么。我需要先回想一下JVM的结构,执行引擎属于JVM的一部分,主要负责执行字节码。那执行引擎具体有哪些组件呢? 首先,记得执行引擎主要包括解释器、即时编译器(JIT)、还有垃圾回收器?或者垃圾回收器是不是属于内存管理部分?可能记混了。对了,垃圾回收器应该属于内存管理,执行引擎主要负责代码执行,所以可能包括解释器、JIT,还有可能包括一些其他的模块,比如本地方法接口? 不过本地方法接口可能属于运行时数据区的一部分?或者执行引擎如何与本地方法库交互呢?可能需要再确认。JVM执行引擎工作流程大概是这样的:加载的字节码由解释器逐行解释执行,而JIT编译器会监控热点代码,将其编译成机器码,提升性能。此外,还有可能涉及到自适应优化,比如HotSpot VM中的特性,这部分是否属于执行引擎的组成部分? 可能执行引擎的主要组成部分是解释器、JIT编译器,以及它们的协作机制。比如,解释器负责快速启动和执行,而JIT在运行时进行优化编译。那除此之外,还有没有其他部分?比如,执行引擎是否包括执行时的栈结构,或者运行时环境?或者这些属于运行时数据区的内容? 另外,执行引擎还可能包括一些底层的处理,比如字节码解释器、JIT编译器生成的本地代码执行模块。可能还需要提到不同的JIT编译器,比如C1和C2编译器,在HotSpot中的分层编译策略。这些都属于执行引擎的一部分。 还有,执行引擎如何与运行时数据区交互?比如,执行引擎需要操作数栈、局部变量表等,但这些属于运行时数据区,执行引擎负责执行指令,处理这些数据结构。所以执行引擎的组件更多是处理执行过程的机制,而不是数据存储本身。 总结一下,执行引擎应该包括: 1. 解释器:逐行解释字节码,执行速度较慢但无需等待编译。 2. JIT编译器:将热点代码编译为本地机器码,提高执行效率。 3. 垃圾回收器:这个可能不属于执行引擎,属于内存管理部分,所以需要排除。 4. 还有可能包括Profiler,用于检测热点代码,供JIT使用。 5. 此外,可能还有自适应优化模块,比如HotSpot中的优化策略。 可能用户需要的回答是解释器、即时编译器以及相关的支持模块,比如Profiler和自适应优化机制。需要确认垃圾回收器不属于执行引擎。另外,执行引擎还可能涉及到本地方法接口的执行,但本地方法库的实现可能由外部提供,执行引擎负责调用。 所以最终的结构可能是: - 解释器 - JIT编译器(包括不同的编译器如C1/C2) - 自适应优化(如热点探测、编译优化策略) - 可能包括本地方法执行的支持,但不确定是否需要单独列出 需要确保回答准确,不要包含错误信息,比如垃圾回收器不属于执行引擎。可能需要参考一些资料确认,比如《深入理解Java虚拟机》中的内容。</think>JVM执行引擎是负责执行字节码的核心组件,主要包括以下部分: --- ### 1. **解释器(Interpreter)** - **功能**:逐行解释字节码并执行,无需等待编译,启动速度快。 - **特点**:执行效率较低(逐条解释),但适合对响应时间敏感的场景(如程序启动时)。 --- ### 2. **即时编译器(JIT Compiler, Just-In-Time Compiler)** - **功能**:将频繁执行的“热点代码”(HotSpot Code)编译为本地机器码,显著提升执行效率。 - **主要类型**(以HotSpot为例): - **C1编译器(客户端编译器)**:针对启动速度优化,适用于桌面应用。 - **C2编译器(服务端编译器)**:针对峰值性能优化,适用于服务器端应用。 - **分层编译(Tiered Compilation)**:结合C1和C2的优势,根据代码热度动态选择优化级别。 --- ### 3. **自适应优化(Adaptive Optimization)** - **功能**:通过运行时分析(Profiling)监控代码执行,动态调整优化策略。 - **热点探测**:识别高频执行的方法或循环(如计数器触发)。 - **去优化(Deoptimization)**:在某些情况下(如优化假设失效),回退到解释模式。 --- ### 4. **其他支持模块** - **字节码预处理器**:某些JVM在解释/JIT编译前对字节码进行简单优化(如方法内联)。 - **本地方法接口(JNI)支持**:执行引擎与本地方法库(Native Libraries)交互,处理`native`方法的调用。 --- ### 执行引擎的工作流程 1. 初始阶段由**解释器**快速执行字节码。 2. 通过**热点探测**识别高频代码。 3. **JIT编译器**将热点代码编译为本地机器码,后续直接执行。 4. **自适应优化**持续调整编译策略,平衡启动时间和运行效率。 --- ### 注意 - **垃圾回收器(GC)**属于JVM的**内存管理子系统**,而非执行引擎的组成部分。 - **运行时数据区**(如堆、栈)为执行引擎提供数据存储,但属于独立模块。 执行引擎通过解释与编译结合的混合模式(如HotSpot VM),在灵活性和性能之间实现了高效平衡。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值