Jvm 系列(十二) JVM的执行引擎全面讲解

news2024/11/16 12:01:48

JVM 执行引擎

1、执行引擎概述

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

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

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

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

1.1、执行引擎的工作过程

执行引擎在执行的过程中究竟需要执行什么样的字节码完全依赖于PC寄存器,每当执行完一项指令操作后,PC寄存器就会更新下一条需要被执行的指令地址。

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

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

image-20230103152657166

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

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

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

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

image-20220327113657114

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

  • 解释器:当Java虚拟机启动时会根据预定义的规范对字节码采用逐行解释的方式执行,将每条字节码文件中的内容“翻译”为对应平台的本地机器指令执行。
  • JIT(Just In Time Compiler)编译器:就是虚拟机将源代码直接编译成和本地机器平台相关的机器语言。

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

  • JDK1.0时代,将Java语言定位为“解释执行”还是比较准确的,再后来,Java也发展出可以直接生成本地代码的编译器。
  • 现在JVM 在执行Java代码的时候,通常都会将解释执行与编译执行二者结合起来进行。

3、机器码、指令、汇编语言

image-20220327113704467

3.1、机器码

各种用二进制编码方式表示的指令,叫做机器指令。开始,人们就用它编写程序,这就是用机器语言。机器语言虽然能够被计算机理解和接受,但和人们的语言差别太大,不易被人们理解和记忆,并且用它编程容易出差错。用它编写的程序一经输入计算机,CPU直接读取运行,因此和其他语言编的程序相比,执行速度最快。机器指令与CPU紧密相关,所以不同种类的CPU所对应的机器指令也就不同。

3.2、指令

由于机器码是有0和1组成的二进制序列,可读性实在太差,于是人们发明了指令。指令就是把机器码中特定的0和1序列,简化成对应的指令(一般为英文简写,如mov,inc等),可读性稍好。由于不同的硬件平台,执行同一个操作,对应的机器码可能不同,所以不同的硬件平台的同一指令(比如mov),对应的机器码也可能不同。

3.3、指令集

不同的硬件平台,各自支持的指令是有差别的。因此每个平台所支持的指令,称之为对应平台的指令集。如常见的:

  • x86 指令集,对应的是 x86 架构的平台。
  • ARM 指令集,对应的是 ARM 架构的平台。

3.4、汇编语言

由于指令的可读性还是太差,于是人们又发明了汇编语言。在汇编语言中,用助记符(Mnemonics)代替机器指令的操作码,用地址符号(Symbol)或标号(Label)代替指令或操作数的地址,在不同的硬件平台,汇编语言对应着不同的机器语言指令集,通过汇编过程转换成机器指令。由于计算机只认识指令码,所以用汇编语言编写的程序还必须翻译成机器指令码,计算机才能识别和执行。

3.5、字节码

字节码是一种中间状态(中间码)的二进制代码(文件),它比机器码更抽象,需要直译器转译后才能成为机器码。字节码主要为了实现特定软件运行和软件环境、与硬件环境无关。

字节码的实现方式是通过编译器和虚拟机器。编译器将原码编译成字节码,特定平台上的虚拟机器将字节码转移为可以直接执行的指令。字节码的典型应用为Java bytecode

3.6、C、C++源程序执行过程

编译过程又可以分成两个阶段:编译和汇编

  • 编译过程:是读取源程序(字符流),对之进行词法和语法分析,将高级语言指令转换为功能等效的汇编代码。
  • 汇编过程:实际上是把汇编语言代码翻译成机器指令的过程。

4.、解释器

4.1、解释器

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

4.2、解释器工作机制(或工作任务)

解释器真正意义上所承担的角色就是一个运行时“翻译者”,将字节码文件中的内容“翻译”为对应平台的本地机器指令执行。当一条字节码指令被解释执行完成后,接着再根据 PC 寄存器中记录的下一条需要被执行的字节码指令执行解释操作。

4.3、解释器分类

在Java的发展历史里,一共有两套解释执行器,即古老的字节码解释器、现在普遍使用的模板解释器。字节码解释器在执行时通过纯软件代码模拟字节码的执行,效率非常低效。而模板解释器将每一条字节码和一个模板函数性关联,模板函数中直接产生这条字节码执行时的机器码,从而很大程度上提高了解释器的性能。

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

  • Interpreter模块:实现了解释器的核心功能。
  • Code模块:用于管理Hotspot VM在与运行时生成的本地机器指令。

4.4、现状

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

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

5、JIT编译器

5.1、 Java代码的执行分类

  • 第一种是将源代码编译成字节码文件,然后在运行时通过解释器将字节码文件转为机器码执行。

  • 第二种是编译执行(直接编译成字节码)。现代虚拟机为了提高执行效率,会使用即时编译技术(JIT,Just In Time)将方法编译成机器码后再执行。

HotSpot VM是目前市面上高性能虚拟机的代表中之一。它采用解释器与即时编译器并存的架构。在Java虚拟机运行时,解释器和即时编译器能够相互协作,各自取长补短,尽力去选择最合适的方式来权衡编译本地代码的时间和直接解释执行代码的时间。

在今天,Java程序的运行性能早已脱胎换骨,已经达到了可以和C/C++程序一较高下的地步。

5.2、问题来了

有些开发人员会感觉诧异,既然Hotspot VM中已经内置了JIT编译器了,那为什么还需要再使用解释器来“拖累”程序的执行性能呢?比如:JRocket VM内部就不包含解释器,字节码全部都依赖即时编译器编译后执行。

首先明确:

  • 当程序启动后,解释器可以马上发挥作用,省去编译的时间,立即执行。(程序刚启动时,执行快)
  • 编译器要想发挥作用,把代码编译成本地代码,需要执行一定的时间。但编译本地代码后,执行效率高。
  • 所以:尽管JRockit VM中程序的执性能会非常高效,但程序在启动时必然需要花费更长的时间来进行编译。对于服务器端应用来说,启动时间并非是关注重点,但对于那些看中启动时间的应用场景而言,或许就需要采用解释器与即时编译器并存的架构来换取一个平衡点。在此模式下**,当Java虚拟机启动时,解释器可以首先发挥作用,而不必等待即使编译器全部编译完成后再执行,**这样可以省去很多不必要的编译时间。随着时间的推移,编译器发挥作用,把越来越多的代码编译成本地代码,获得更高效的执行效率。
  • 同时,解释器执行在编译器进行激进优化不成立的时候,作为编译器的“逃生门”。

5.3、HotSpot VM的执行方式

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

5.4、JIT编译器

概念解释:

  • Java语言的“编译期“其实是一段”不确定“的操作过程,因为他可能是指一个前端编译器(其实叫”编译器的前端“更准确一些)把 .java文件转变为 .class文件的过程。
  • 也可能是指虚拟机的后端运行期编译器(JIT 编译器, Just In Time Compiler)把字节码转变成机器码的过程。
  • 还可能是指使用静态提前编译器(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。

5.5、热点代码及探测方式

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

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

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

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

  • 方法调用计数器用于传统方法的调用次数。
  • 回边计数器则用于统计循环执行的循环次数。

5.6、方法调用计数器

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

这个阈值可以通过虚拟机参数 -XX:CompoleThreshold 来认为设定。

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

image-20220327113713646

  • 热度衰减:
  • 如果不做任何设置,方法调用计数器统计的并不是方法调用的绝对次数,而是一个相对的执行频率,即一段时间值之内方法被调用的次数。当超过一定的时间限度,如果方法的调用次数仍然不足以让它提交给即时编译器,那这个方法的调用计数器就会被减少一半,这个过程称为方法调用计数器的衰减(Counter Decay),而这段时间就称为此方法的半衰期(Counter Half Life Time)。
  • 进行热度衰减的动作是在虚拟机进行垃圾收集时顺便进行的,可以使用虚拟机参数 -XX:-UseCounterDecay 来关闭热度衰减,让方法计数器调用的绝对次数,这样,只要系统运行时间足够长,绝大部分方法都会被编译成本地代码。
  • 另外,可以使用 -XX:CounterHalfLifeTime参数设置半衰周期的时间,单位是秒。

5.7、回边计数器

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

image-20220327113722693

6、HotSpot VM可以设置程序执行的方式

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

  • -Xint:完全采用解释模式执行程序。
  • -Xcomp:完全采用即时编译器执行程序。如果即时编译器出现问题,解释器会介入执行。
  • -Xmixed:采用解释器+即时编译器的混合模式共同实行程序。

7、HotSpot VM中 JIT 分类

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

  • -client:指定Java 虚拟运行在 Client 模式下,并使用C1编译器;
    • C1编译器会对字节码进行简单和可靠的优化,耗时短,以达到更快的编译速度。
  • -server:指定Java虚拟机运行在Server 模式下,并使用C2编译器。
    • C2进行耗时较长,以及激进优化,但优化的代码执行效率更高。

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

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

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

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

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

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

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

总结

一般来讲,JIT编译出来的机器码性能比解释器高,C2编译器启动时长比C1编译器慢,系统稳定执行以后,C2编译器执行速度远远快于C1编译器。自JDK10起,HotSpot又加入了一个全新的即时编译器:Graal编译器。编译效果短短几年就追评了C2编译器。使用 -XX : +UnlocalExperimentalVMOptions -XX:+UseJVMCICompiler去激活,才可以使用。JDK9引入了AOT编译器(静态提前编译器,Ahead Of Time Compiler)。Java 9引入了实验性 AOT 编译工具 jaotc。它借助了 Graal 编译器,将所输入的Java类文件转换为机器码,并存放至生成的动态共享库之中。所谓AOT编译器,是与即时编译相对立的一个概念,我们知道,即时编译指的是在程序的运行过程中,将字节码转换为可在硬件上直接运行的机器码,并部署至托管环境中的过程。而 AOT 编译指的则是,在程序运行之前,便将字节码转换为机器码的过程。

好处:

  • Java 虚拟机加载已经预编译成二进制库,可以直接执行。不必等待即使编译器预热,减少Java应用给人带来“第一次运行慢”的不良体验。

缺点:

  • 破坏了java“一次编译,到处运行”,必须为每个不同硬件、OS编译对应的发行包。
  • 降低了 Java 链接过程的动态性,加载的代码在编译期就必须全部已知。
  • 还需要继续优化中,最初只支持Linux x64 java base。

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

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

相关文章

国产直流马达驱动芯片SS6216的功能参数以及应用

直流有刷电机驱动芯片SS6216是为消费类产品,玩具和其他低压或者电池供电的运动控制类应用提供了一个集成的有刷电机驱动器解决方案。是为低电压下工作的系统而设计的直流电机驱动集成电路,单通道低导通电阻。具备电机正转/反转/停止/刹车四个功能。 直流…

STL剖析(二):容器底层数据结构及常见用法

一.概述 本文主要聚焦于STL容器,STL完整的容器分类体系如下所示,下文将逐一对各个容器底层的数据结构以及常见用法进行介绍。 测试环境:Ubuntu 22.04 g 11.3.0 二.顺序容器 顺序容器都对应着线性数据结构。 2.1 array array的使用需要引…

6.2 微服务-SpringBoot

目录 6.2.1 SpringBoot 6.2.1.1 什么是Spring Boot 6.2.1.2 SpringBoot的特点 6.2.2 快速入门 6.2.2.1 创建工程 6.2.2.2 引入依赖 6.2.2.3 启动类 6.2.2.4 controller 6.2.2.5 测试 6.2.3 注解与属性注入 6.2.3.1 注解 6.2.3.1.1 EnableAutoConfiguration 6.2.3.1…

leetcode 240. 搜索二维矩阵 II-java题解

题目所属分类 从右上角出发往下遍历 倒是也可以二分 原题链接 编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性: 每行的元素从左到右升序排列。 每列的元素从上到下升序排列。 代码案例: 输入:m…

加解密与HTTPS(4)

您好,我是湘王,这是我的CSDN博客,欢迎您来,欢迎您再来~ 在互联网应用中,安全性问题已经越来越突出。从DDoS攻击、矿机劫持、乌云事件(白帽子变成黑帽子),到窃听、偷拍、强…

Word控件Spire.Doc 【Table】教程(1):在 Word 中创建表格-C#VB.NET

Spire.Doc for .NET是一款专门对 Word 文档进行操作的 .NET 类库。在于帮助开发人员无需安装 Microsoft Word情况下,轻松快捷高效地创建、编辑、转换和打印 Microsoft Word 文档。拥有近10年专业开发经验Spire系列办公文档开发工具,专注于创建、编辑、转…

spring源码-资源资源加载器

Spring资源抽象Resource Spring对各种底层资源,比如文件系统中的一个文件,classpath上的一个文件,或者一个网络URL,统一抽象为接口Resource来表示 因为每个底层文件都可以以一个只读InputStream的方式打开,所以Resource接口继承…

ModuleNotFoundError: No module named ‘cs231n‘

在colab上完成cs231n的作业时发现,报了No module named cs231n’这个错误,查询后也没有找到合适的答案 仔细检查,发现是没有找到assignment1下的cs231n文件夹,然后去网站核对视频教程,发现没有搞错,视频中…

浮点数的储存

浮点数的储存一.浮点数的三段式(S,E,M)1.如何放入2.如何取出二.为什么浮点数不能直接比较三.解释第一个问题我们都知道整形在内存中是按照补码的形式储存的,但是浮点数的储存却和整数的截然不同,浮点数没有所谓是原反补并且浮点数…

SSM框架学习记录-MyBatisPlus_day01

1.入门案例与简介 MybatisPlus是基于MyBatis框架基础上开发的增强型工具,旨在简化开发、提供效率 未使用MybatisPlus时,在dao接口中的代码如下: Mapper public interface UserDao {Select("select * from user where id#{id}")publ…

冬日宅家选哪款投影仪比较好?极米H5陪你温暖过冬天

随着室外温度的逐步下降,寒冬也真的来了。相信对于许多朋友来说,宅家是冬季最惬意的时光,就是开着空调、电暖风、暖气,在温暖的室内,再打开投影仪,用超大屏追剧、看看电影,听听美妙的音乐&#…

光伏二次设备概述

概述 分布式光伏发电项目一般根据并网的电压等级分为380V和10KV。一般电压等级为380V低压并并网基本不涉及到什么二次产品,通常采用光伏并网柜就能解决,常见的并网设备为防孤岛保护装置和电能质量在线监测装置为主。而10KV并网的光伏容量一般处于1MWP到1…

加强企业数据库安全的行为准则

现在大多数企业都拥有可靠的网络安全程序,这些程序利用多种控件来实现深度防御安全性。通过这些程序,企业服务器得到加固,企业端点得到保护,监控工具也得以部署。还能够消除来自端点设备的高度敏感信息,并整合企业系统…

基于jsp+sevlet+mysql实验室设备管理系统

基于jspsevletmysql实验室设备管理系统一、系统介绍二、功能展示1.通知公告(学生)2.实验设备借用申请(学生)3.设备借用记录(学生)4.实验室预约申请(老师)5.实验室预约记录(老师)6.实验设备借用申请(老师)7.设备借用记录(老师)8.通知公告(管理员)9.实验室管理(管理员)10.设备管理…

QA | 关于可编程信号发生器,您在使用中可能遇到的问题

Q1:为什么信号源插在电脑上会显示电压不足? A:通常需要比普通电脑USB接口能提供更大的功率,需要高达2.0A的电流,超出了许多老式 USB 端口的水平。可以通过多种方式满足这一要求。适配器、USB 3.0计算机/笔记本电脑端口…

python——Matplotlib之fill_between函数

Matplotlib是Python提供的一套基于NumPy的绘图工具包,用Python实现与MATLAB相似的命令API,十分适合交互式绘制图表,成为Python中应用非常广的绘图工具包之一。 在对数据可视化时,为了突出某一段数据需要对部分区域进行填充处理。…

用这4招优雅的实现Spring Boot 异步线程间数据传递

Spring Boot 自定义线程池实现异步开发相信看过文章都了解,但是在实际开发中需要在父子线程之间传递一些数据,比如用户信息,链路信息等等 比如用户登录信息使用ThreadLocal存放保证线程隔离,代码如下: /*** author 公…

认监委调整《有机产品认证目录》

认监委关于调整《有机产品认证目录》的公告为进一步完善有机产品认证制度,规范有机产品认证活动,促进有机产业发展,根据《有机产品认证管理办法》(质检总局令第155号)和《有机产品认证实施规则》(认监委201…

大数据编程期末大作业

大数据编程期末大作业 文章目录大数据编程期末大作业一、Hadoop基础操作二、RDD编程三、SparkSQL编程四、SparkStreaming编程一、Hadoop基础操作 在HDFS中创建目录 /user/root/你的名字 例如李四同学 /user/root/lisi 首先我们需要启动hdfs,我们直接在终端输入如下命…

JavaScript for 循环

文章目录JavaScript for 循环JavaScript 循环使用for循环不同类型的循环For 循环语句 1语句 2语句 3For/In 循环JavaScript for 循环 循环可以将代码块执行指定的次数。 JavaScript 循环 如果您希望一遍又一遍地运行相同的代码,并且每次的值都不同,那么…