Java --- JVM的执行引擎

目录

一、执行引擎概述 

1.1、执行引擎的工作过程

二、Java代码编译和执行的过程

三、解释器

 3.1、解释器工作机制

3.2、解释器分类

3.3、解释器现状

四、JIT编译器

五、热点代码及探测方式

六、方法调用计数器

 6.1、热点衰减

七、回边计数器

八、HotSpot VM设置程序执行方式

8.1、HotSpot VM中JIT分类

8.2、C1和C2编译器不同优化策略

8.2.1、分层编译策略


一、执行引擎概述 

1、执行引擎是Java虚拟机核心的组成部分之一。

2、“虚拟机”是一个相对于“物理机”的概念,这两种机器都有代码执行能力,其区别是物理机的执行引擎是直接建立在处理器、缓存、指令集和操作系统层面上的,而虚拟机的执行引擎则是由软件自行实现的,因此可以不受物理条件制约地定制指令集与执行引擎的结构体系,能够执行那些不被硬件直接支持的指令集格式。

3、JVM的主要任务是负责装载字节码到其内部,但字节码并不能够直接运行在操作系统之土,因为字节码指令并非等价于本地机器指令,它内部包含的仅仅只是一些能够被JVM所识别的字节码指令、符号表,以及其他辅助信息。

4、那么,如果想要让一个Java程序运行起来,执行引擎(Execution Engine)的任务就是将字节码指令解释/编译为对应平台上的本地机器指令才可以。简单来说,JVM中的执行引擎充当了将高级语言翻译为机器语言的译者。

1.1、执行引擎的工作过程

1、执行引擎在执行的过程中究竟需要执行什么样的字节码指令完全依赖于PC寄存器。

2、每当执行完一项指令操作后,PC寄存器就会更新下一条需要被执行的指令地址。

3、当然方法在执行的过程中,执行引擎有可能会通过存储在局部变量表中的对象引用准确定位到存储在Java堆区中的对象实例信息,以及通过对象头中的元数据指针定位到目标对象的类型信息。

小结:从外观上来看,所有的Java虚拟机的执行引擎输入、输出都是一致的:输入的是字节码二进制流,处理过程是字节码解析执行的等效过程,输出的是执行结果。

二、Java代码编译和执行的过程

 大部分的程序代码转换成物理机的目标代码或虚拟机能执行的指令集之前,都需要经过上图中的各个步骤。

Java代码编译是由Java源码编译器来完成。

Java字节码的执行是由JVM执行引擎来完成。

什么是解释器(Interpreter),什么是JIT编译器?

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

JIT (Just In Time Compiler)编译器:就是虚拟机将源代码直接编译成和本地机器平台相关的机器语言。

为什么Java是半编译半解释型语言?

JDK1.0时代,将Java语言定位为“解释执行”还是比较准确的。再后来,Java也发展出可以直接生成本地代码的编译器。

现在JVM在执行Java代码的时候,通常都会将解释执行与编译执行二者结合起来讲行。

三、解释器

JVM设计者们的初衷仅仅只是单纯地为了满足Java程序实现跨平台特性,因此避免采用静态编译的方式直接生成本地机器指令,从而诞生了实现解释器在运行时采用逐行解释字节码执行程序的想法。

 3.1、解释器工作机制

       解释器真正意义上所承担的角色就是一个运行时“翻译者”,将字节码文件中的内容“翻译”为对应平台的本地机器指令执行。

        当一条字节码指令被解释执行完成后,接着再根据Pc寄存器中记录的下一条需要被执行的字节码指令执行解释操作。

3.2、解释器分类

     在Java的发展历史里,一共有两套解释执行器,即古老的字节码解释器、现在普遍使用的模板解释器。 

字节码解释器在执行时通过纯软件代码模拟字节码的执行,效率非常低下。

而模板解释器将每一条字节码和一个模板函数相关联,模板函数中直接产生这条字节码执行时的机器码,从而很大程度上提高了解释器的性能。

在HotSpot VM中,解释器主要由Interpreter模块和Ctode模块构成。

     Interpreter模块:实现了解释器的核心功能

     Code模块:用于管理HotSpot VM在运行时生成的本地机器指令

3.3、解释器现状

1、由于解释器在设计和实现上非常简单,因此除了Java语言之外,还有许多高级语言同样也是基于解释器执行的,比如Python、Perl、Ruby等。但是在今天,基于解释器执行已经沦落为低效的代名词,并且时常被一些C/C++程序员所调侃。|

2、为了解决这个问题,JVM平台支持一种叫作即时编译的技术。即时编译的目的是避免函数被解释执行,而是将整个函数体编译成为机器码,每次函数执行时,只执行编译后的机器码即可,这种方式可以使执行效率大幅度提升。

3、不过无论如何,基于解释器的执行模式仍然为中间语言的发展做出了不可磨灭的贡献。

四、JIT编译器

1、Java语言的“编译期” 其实是一段“不确定”的操作过程,因为它可能是指一个前端编译器,把.java文件转变成.class文件的过程。

2、也可能是指虚拟机的后端运行编译器(JIT 编译器,Just In Time Compiler)把字节码转变成机器码的过程。

3、还可能是指使用静态提前编译器(AOT 编译器,Ahead Of Time Compiler)直接把.java文件编译成本地机器代码的过程。

前端编译器: Sun 的 Javac、Eclipse JDT 中的增式编译器(ECJ)。

JIT编译器: HotSpot VM 的 C1 、C2编译器。

AOT编译器:GNU Compiler for the Java (GCJ)、Excelsior JET。

五、热点代码及探测方式

当然是否需要启动JIT编译器将字节码直接编译为对应平台的本地机器指令,则需要根据代码被调用执行的频率而定。关于那些需要被编译为本地代码的字节码,也被称为“热点代码”,JIT编译器在运行时会针对那些频繁被调用的“热点代码”做出深度优化,将其直接编译为对应平台的本地机器指令,以此提升Java程序的执行性能。

1、一个被调用多次的方法,或者是一个方法体内部循环次数较多的循环体都可以被称为“热点代码”,因此都可以通过JIT编译器编译为本地机器指令,由于这种编译方式发生在方法的执行过程中,因此也被称为栈上替换,或简称为OSR(On Stack Replacement)。

2、一个方法究竟被调用多少次,或者一个循环究竟需要执行多少次循环才可以达到这个标准?必须需要一个明确的阈值,JIT编译器才会将这些“热点代码”编译为本地机器指令执行,主要依靠热点探测功能。

3、目前HotSpot VM所采用的热点探测方式是基于计数器的热点探测。

4、采用基于计数器的热点探测,HotSpot VM将会为每一个方法建立2个不同类型的计数器,分别为方法调用计数器(Invocation Counter)和回边计数器(Back Edge Counter)。

    ①、方法调用计数器用于统计方法的调用次数。

    ②、回边计数器则用于统计循环体执行的循环次数。

六、方法调用计数器

1、这个计数器就用于统计方法被调用的次数,它默认阈值在Client模式下是1500次,在Server模式下是10000次。超过这个阈值,就会触发JIT阈值。

2、这个阈值可以通过虚拟机参数-XX:CompileThreshold来人为设定。

3、当一个方法被调用时,会先检查该方法是否存在被JIT编译过的版本,如果存在,则优先使用编译后的本地代码来执行。如果不存在已被编译过的版本,则将此方法的调用计数器值加1,然后判断方法调用计数器与回边计数器值之和是否超过方法调用计数器的阈值。如果已超过阈值,那么将会向即时编译器提交一个该方法的代码编译请求。

 

 6.1、热点衰减

1、如果不做任何设置,方法调用计数器统计并不是方法调用的绝对次数,而是一个相对的执行频率,即一段时间之内方法被调用的次数。当超过一定的时间限度,如果方法的调用次数仍然不足以让它提交给即时编译器编译,那这个方法的调用计数器就会被减少一半,这个过程称为方法调用计数器就会被减少一半,这个过程被称为方法调用计数器热度的衰减(Counter Decay),而这段时间就称为此方法统计的半衰周期(Counter Half Life Time)。

2、进行热度衰减的动作是在虚拟机进行垃圾收集时顺便进行的,可以使用虚拟机参数-XX:-UseCounterDecay来关闭热度衰减,让方法计数器统计方法调用的绝对次数,这样,只要系统运行时间足够长,绝大部分方法都会被编译为本地代码。

3、可以使用-XX:CounterHalfLifeTime参数设置半衰周期的时间,单位是秒。

七、回边计数器

作用是统计一个方法中循环体代码执行的次数,在字节码中遇到控制流向后跳转的指令称为“回边”(Back Edge).显然,建立回边计数器统计的目的就是为了触发OSR编译。

八、HotSpot VM设置程序执行方式

缺省情况下HotSpot VM是采用解释器与即时编译器并存的架构,开发人员也可以根据具体情况的应用场景,通过命令显式地为Java虚拟机指定在运行时到底是完全采用解释器执行,还是完全采用即时编译器执行。

①、-Xint:完全采用解释器模式执行程序;

②、-Xcomp:完全采用即时编译器模式执行程序。如果即时编译出现问题,解释器会介入执行。

③、-Xmixed:采用解释器+即时编译器的混合模式共同执行程序。

参考代码:

public class IntCompTest {public static void main(String[] args) {long start = System.currentTimeMillis();testPrimeNumber(1000000);long end = System.currentTimeMillis();//-Xint:11740ms -Xcomp:446ms -Xmixed:438msSystem.out.println("花费的时间为:" + (end - start));}public static void testPrimeNumber(int count){for (int i = 0; i < count; i++) {//计算100以内的质数for (int j = 2; j <= 100 ; j++) {for (int k = 2; k <= Math.sqrt(j); k++) {if (j % k == 0){continue;}}}}}
}

8.1、HotSpot VM中JIT分类

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

1、-client:指定Java虚拟机运行在Client模式下,并使用C1编译器。C1编译器会对字节码进行简单和可靠的优化,耗时短。以达到更快的编译速度。 

2、-server:指定Java虚拟机运行在Server模式下,并使用C2编译器。C2进行耗时较长的优化,以及激进优化。但优化的代码执行效率更高。

8.2、C1和C2编译器不同优化策略

1、在不同的编译器上有不同的优化策略,C1编译器上主要有方法内联,去虚拟化、冗余消除。

    ①、方法内联:将引用的函数代码编译到引用点处,这样可以减少栈帧的生成,减少参数传递以及跳转过程。

    ②、去虚拟化:对唯一的实现类进行内联。

    ③、冗余消除:在运行期间把一些不会执行的代码折叠掉。

2、C2的优化主要是在全局层面,逃逸分析是优化的基础。基于逃逸分析在C2上有如下几种优化:

    ①、标量替换:用标量值代替聚合对象的属性值

    ②、栈上分配:对于未逃逸的对象分配对象在栈而不是在堆。

    ③、同步消除:消除同步操作,通常指synchronized

8.2.1、分层编译策略

     程序解释执行(不开启性能监控)可以触发C1编译,将字节码编译成机器码,可以进行简单优化,也可以加上性能监控,C2编译会根据性能监控信息进行激进优化。

     在Java7版本后,开发人员在程序中显示指定命令”-server“时,默认将会开启分层策略,由C1编译器和C2编译器相互协作共同来执行编译任务。

总结:1、一般情况下,JIT编译出来的机器码性能比解释器高。2、C2编译器启动时长比C1编译器慢,系统稳定执行以后,C2编译器执行速度远远快于C1编译器。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/140478.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

axios1.5取消请求,中断请求的方法

给input的onchange绑定事件 引入axios,使用axios.CancelToken.source()创建标记 实例中,把cancelToken的值填上

CSS常用示例100+ 【目录】

目前已有文章 11 篇 本专栏记录的是经常使用的CSS示例与技巧&#xff0c;主要包含CSS布局&#xff0c;CSS特效&#xff0c;CSS花边信息三部分内容。其中CSS布局主要是列出一些常用的CSS布局信息点&#xff0c;CSS特效主要是一些动画示例&#xff0c;CSS花边是描述了一些CSS相关…

Python参数传递,从入门到精通

Python是一种非常灵活的编程语言&#xff0c;以多种方式定义和调用函数。其中一个关键方面是参数传递的灵活性。在Python中&#xff0c;可以通过位置、关键字、默认值和可变长度参数等多种方式来传递参数。 1. 位置参数 位置参数是最常见的参数传递方式。当调用一个函数时&am…

node插件express(路由)的插件使用(二)——cookie 和 session的基本使用区别

文章目录 前言一、express 框架中的 cookie0、cookie的介绍和作用1. 设置cookie2.删除cookie3.获取cookie&#xff08;1&#xff09;安装cookie-parser&#xff08;2&#xff09;导入cookie-parser&#xff08;3&#xff09;注册中间件&#xff08;4&#xff09;获取cookie&…

使用Python分析时序数据集中的缺失数据

大家好&#xff0c;时间序列数据几乎每秒都会从多种来源收集&#xff0c;因此经常会出现一些数据质量问题&#xff0c;其中之一是缺失数据。 在序列数据的背景下&#xff0c;缺失信息可能由多种原因引起&#xff0c;包括采集系统的错误&#xff08;例如传感器故障&#xff09;…

Intel® DevCloud for oneAPI SYCL编程项目实践

问题陈述 实验所用的硬件环境和软件环境 本次实验使用oneAPI中支持SYCL编程模型的C编译器&#xff0c;使用英特尔oneAPI Developer Cloud服务&#xff0c;可以免安装额外环境&#xff0c;利用CPU作为主机&#xff08;Host&#xff09;&#xff0c;同时利用GPU作为设备&#xf…

LCA

定义 最近公共祖先简称 LCA&#xff08;Lowest Common Ancestor&#xff09;。两个节点的最近公共祖先&#xff0c;就是这两个点的公共祖先里面&#xff0c;离根最远的那个。 性质 如果 不为 的祖先并且 不为 的祖先&#xff0c;那么 分别处于 的两棵不同子树中&#…

【机试题】LazyIterator迭代器懒加载问题

将下面这个未完成的Java工具类补充完成&#xff0c;实现懒加载的功能&#xff0c;该类需要实现Iterable接口&#xff0c;能够遍历所有数据。具体要求如下&#xff1a; 工具类提供了一个ValueLoader接口&#xff0c;用于获取数据&#xff0c;其中ValueLoader的接口定义为&#x…

修改django开发环境runserver命令默认的端口

runserver默认8000端口 虽然python manage.py runserver 8080 可以指定端口&#xff0c;但不想每次runserver都添加8080这个参数 可以通过修改manage.py进行修改&#xff0c;只需要加三行&#xff1a; from django.core.management.commands.runserver import Command as Ru…

二十四、W5100S/W5500+RP2040树莓派Pico<PHY的状态模式控制>

文章目录 1. 前言2. 相关简介2.1 简述2.2 原理2.3 优点&应用 3. WIZnet以太网芯片4. PHY模式配置测试4.1 程序流程图4.2 测试准备4.3 连接方式4.4 相关代码4.5 测试现象 5. 注意事项6. 相关链接 1. 前言 W5100S/W5500不仅支持自动PHY自动协商&#xff0c;而且支持用户自定义…

自动生成Form表单提交在苹果浏览器中的注意事项

以下是本人在公司旧系统中看到的该段代码 function Post(URL, PARAMTERS) {//创建form表单var temp_form document.createElement("form");temp_form.action URL;//如需打开新窗口&#xff0c;form的target属性要设置为_blanktemp_form.target "_blank"…

[EFI]技嘉 Z490 VISION G i5-10500 电脑 Hackintosh 黑苹果引导文件

硬件配置 硬件型号驱动情况主板技嘉 Z490 VISION G CLPC controller Z490芯片组&#xff09;处理器英特尔 Core i5-10500 3.10GHz 六核已驱动内存16GB&#xff08; 威到DDR42655MHz8GBx 2〕已驱动硬盘SSDSC2BB150G7R (150 GB/ 国态硬盘&#xff09;已驱动显卡AMD Radeon RX 58…

[论文阅读] CLRerNet: Improving Confidence of Lane Detection with LaneIoU

Abstract 车道标记检测是自动驾驶和驾驶辅助系统的重要组成部分。采用基于行的车道表示的现代深度车道检测方法在车道检测基准测试中表现出色。通过初步的Oracle实验&#xff0c;我们首先拆分了车道表示组件&#xff0c;以确定我们方法的方向。我们的研究表明&#xff0c;现有…

Python之字符串、正则表达式练习

目录 1、输出随机字符串2、货币的转换&#xff08;字符串 crr107&#xff09;3、凯撒加密&#xff08;book 实验 19&#xff09;4、字符替换5、检测字母或数字6、纠正字母7、输出英文中所有长度为3个字母的单词 1、输出随机字符串 编写程序&#xff0c;输出由英文字母大小写或…

ESP32 BLE特征值示例

键盘特征值初始化示例 void BleKeyboard::begin(void) {BLEDevice::init(deviceName);BLEServer* pServer BLEDevice::createServer();pServer->setCallbacks(this);hid new BLEHIDDevice(pServer);inputKeyboard hid->inputReport(KEYBOARD_ID); // <-- input R…

大洋钻探系列之二IODP 342航次是干什么的?(上)

本文简单介绍一下大洋钻探IODP 342航次&#xff0c;从中&#xff0c;我们一窥大洋钻探航次的风采。 IODP342的航次报告在网络上可以下载&#xff0c;英文名字叫《Integrated Ocean Drilling ProgramExpedition 342 Preliminary Report》&#xff0c;航次研究的主要内容是纽芬兰…

【Linux】第十五站:环境变量

文章目录 一、进程相关的一些概念1.一些常见的概念2.对于并发3.**进程切换** 二、环境变量1.PATH环境变量2.HOME环境变量3.SHELL环境变量4.env5.系统调用接口与环境变量6.什么是环境变量&#xff1f;7.命令行参数8.main函数的第三个命令行参数9.如何验证环境变量是可以被继承的…

前端面试之事件循环

什么是事件循环 首先&#xff0c; JavaScript是一门单线程的语言&#xff0c;意味着同一时间内只能做一件事&#xff0c;这并不意味着单线程就是阻塞&#xff0c;而是实现单线程非阻塞的方法就是事件循环 在JavaScript中&#xff0c;所欲任务都可以分为&#xff1a; 同步任务…

【RPC】前传

前传 本地程序用的go语言&#xff0c;想把main.go程序当中一些计算工作放到服务器上进行&#xff0c;而只需要把结果给我即可。由于平台上暂时不能运行Go代码&#xff0c;所以写的是python文件。 1、主要是使用ssh依赖进行连接&#xff0c;但是大概率是需要手动添加的&#xf…

带头+双向+循环链表

前言&#xff1a; 前面我们已经学习了单链表的结构及其功能特点&#xff0c;也了解了单链表在实现一些功能时出现的一些缺点&#xff0c;比如在删除某个节点前面一个节点时就需要再开一个变量来存放前面一个节点的信息&#xff0c;这样就显得不灵活&#xff0c;为了使链表实现功…