JVM深入学习(十一)-执行引擎

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

1. 概述

  1. 执行引擎是Java虚拟机核心的组成部分之一
  2. 虚拟机是相对于物理机而言的,这两种机器都有执行引擎,物理机执行引擎是基于cpu和操作系统来说的,指令集与硬件有深度绑定,而虚拟机执行引擎是由软件自己实现的,因为可以使用硬件不支持的指令集.
  3. JVM的作用就是将字节码加载到内存中,解释编译为机器指令使操作系统能够识别执行.而执行引擎在其中起到的作用就是将字节码指令转换为机器指令.
  4. 执行引擎的工作过程:
  1. 执行引擎从pc寄存器中获取要执行的指令索引
  2. 使用指令索引从操作数栈中获取要执行的指令
  3. 将指令转换为具体的机器指令
  4. 交给操作系统执行
  1. 执行引擎输入的是二进制流,处理过程就是将字节码转换为机器指令的过程,输出的就是执行结果.

2.java代码编译执行的过程

解释器:

jvm启动时根据语法规范对字节码逐行解释执行,将每行字节码文件的内容翻译成本地平台的机器指令执行

JIT编译器:

jvm直接将代码翻译成本地机器指令

jdk1.0时代,jvm只能通过解释器对字节码逐行解释执行,之后增加了可以直接生成本地代码的编译器,所以java是一种半解释半编译的语言

3. 机器码,指令,汇编语言概述

3.1 机器码

机器码又叫机器指令,是计算机能够识别运行的代码,由二进制编写.

机器码由于cpu能够直接识别执行,所以执行速度最快.

但是二进制与人类语言差别较大,直接编写二进制程序难度较高,容易出错.

3.2 指令

为了解决二进制机器码难以阅读,编写的情况,就出现了指令.

指令是指将特定的0/1二进制码简化成一个简单的英文单词(一般情况都是英文单词),如mov,inc等,便于阅读,相比于机器码来说,更容易被人类理解.

指令与硬件平台关联比较深,同一个指令,不同的硬件平台所对应的机器码可能就是不一样的.

总结: 指令就是特定机器码的抽象.


3.3 指令集

不同的平台对应的指令就称为指令集

如 X86平台对应的就是X86指令集

ARM平台对应的就是ARM指令集

3.4 汇编语言

指令虽然相比于二进制机器码简单易读了许多,但是可读性还是比较差,所以又发明了汇编语言

汇编语言中用助记符来代替机器指令的操作码,用地址符号(Symbol)或标记(Label)代替指令或操作数的地址

汇编语言还是要解释为指令才能在各个平台执行.

3.5 高级语言

在汇编语言的基础上,又开发出了高级语言,更接近人的语言,如java python等语言

高级语言想要在平台执行也是要转为指令才能在cpu执行的

3.6 字节码

字节码是一种特殊状态的文件,与硬件条件无关(跨平台)

字节码需要执行引擎来解释编译为指令,再由cpu执行

字节码的标准应用: java byteCode

3.7 总结


4. 解释器

解释器将字节码逐行解释为本地机器指令执行.

4.1 解释器的分类

解释器分为字节码解释器(古老)和模板解释器(现在普遍使用)

字节码解释器: 采用纯软件代码的形式模拟字节码的运行

模板解释器: 将每条字节码与一个模板函数关联,由模板函数直接生成机器码,极大提高解释器的性能

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

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

Code 管理生成的机器码指令

4.2 解释器的作用和弊端

解释器将字节码转换为机器指令的方式解释运行源代码

弊端: 无论是字节码解释器还是模板解释器,效率都比较低下.

5. JIT编译器

JIT(Just in Time)编译器被称之为即时编译器

将执行过的代码缓存至方法区,再次执行时就不用再次编译,而是直接转为机器码执行,速度极高

解释器和JIT编译器同时运行的原因:

JIT编译器的效率极高,为什么还要使用解释器呢?

主要是因为解释器与JIT编译器相比:

  1. 解释器在jvm启动的时候就可以对字节码进行编译运行,而不用等待代码全部编译完成后再执行,响应速度快,
  2. 而JIT编译器就必须要代码编译完成后将本地代码解释为指令后才能够执行,响应速度较慢
  3. 相对来说,JIT编译器在随着代码的执行,利用热点代码探测技术,速度会越来越快,所以更适合对响应时间要求不高的场景,如服务端应用.
  4. JRocket就是只有JIT编译器的jvm
  5. 当JIT编译器激进优化不成立的时候,解释器可以充当备用机,增加了可靠性

如何证明JIT的存在:

热机状态: 服务器启动运行了一段时间之后

冷机状态: 服务器刚启动的时候

将热机状态的流量切给冷机状态下的服务器时,会导致冷机状态的服务器直接挂掉,这个例子就说明JIT的存在


5.1 热点代码探测

JIT编译器需要根据字节码执行的频率来确定是否要将字节码编译成本地机器指令,执行频率较高的代码就称为热点代码,JIT编译器就针对这些执行频率较高的代码进行深度优化,编译成本地机器指令,在下次执行时就不用再使用解释器解释了,而是直接执行本地机器指令,提升JAVA性能

5.1.1 热点代码

一个多次执行的代码就可以被称为热点代码,包括循环体中的循环代码,这些代码都发生方法执行过程中,因此也被称为栈上替换(OSC ON Stack ReplaceMent)

5.1.2 热点探测

热点探测功能:

HotSpotVM采用的热点探测方式主要是基于计数器的探测,共有两种计数器

  1. 方法调用计数器 Invocation Counter: 统计方法调用的次数
  2. 回边计数器 Back Edge Counter: 统计循环体执行的次数

探测次数:

探测次数Client和Server模式有不同的阈值

Client默认1500次 Server默认10000次

超过阈值就会触发JIT编译

阈值可以通过 -XX:compileThreshold来设定

方法计数器的过程:

  1. 判断是否编译过,如果是直接调用编译过的机器指令
  2. 如果没有,方法计数器+1
  3. 判断+1后是否超过阈值,如果没有执行解释器,逐行解释执行
  4. 如果超i过阈值,提交编译请求到JIT编译器,编译器编译后进行代码缓存(方法区)

回边计数器与方法计数器类似

唯一的区别在于,判断是否超过阈值时,是判断两个计数器之和.

可以理解,因为回边计数器中循环体内可能调用该方法

5.1.3 热度衰减

默认情况下,计数器的阈值并不是绝对次数,如果是绝对次数,在运行一段时间后,所有的代码理论上都会被JIT编译器编译.

所以,此阈值指定的是某一段时间内调用的方法次数,超过此时间,则此方法的方法计数器减少一半调用次数,这个过程称为衰减,这一段时间称为半衰周期

可以通过虚拟机参数  -XX: -UseCounterDecay关闭热度衰减功能.

也可以通过 -XX:CounterHalfLifeTime设置半衰周期


5.2 设置程序执行方式

默认情况下,HotSpot是解释器和编译器并存的方式,但是也可以通过命令指定HotSpot使用某一种

-Xint: 完全采用解释器

-Xcomp: 完全采用即时编译器

-Xmixed: 混合模式 默认

默认情况下 mixed

命令行设置模式

# 解释器java -Xint-version# 编译器java -Xcomp-version


5.3 C1和C2编译器

JIT细分可以分为C1和C2两种编译器

  1. C1是Client编译器,对字节码进行简单可靠的优化,耗时短
  2. C2是Server编译器,对字节码进行耗时较长的优化,以及激进优化,优化后的代码效率更高

通过参数 -clinet 和 -server指定编译器

64位jdk默认是-server模式,所以默认使用了C2编译器

5.3.1 C1和C2的优化策略

C1:

  1. 方法内联: 将引用的方法代码编译到引用点处,减少栈帧的生成,减少参数传递以及跳转过程
  2. 去虚拟化: 如果一个接口只有一个实现类,就减少内联.
  3. 冗余消除: 在运行期间将一些不会执行的代码折叠掉

C2: 基于逃逸分析,可以看下堆那一节的逃逸分析

  1. 栈上分配
  2. 变量替换
  3. 同步消除

但是并不是-Server就会全部使用C2编译器,而是开启了分层策略,基于性能监控的情况下,首先使用C1,根据性能监控情况使用C2

可以理解为 -client 只会使用C1

-server 是C1和C2都会使用.


5.4 总结

JIT编译器编译出来的机器码的性能都比解释器高.

C2比C1慢,但是再运行一段时间后,C2的速度远高于C1

6. 扩展: Graal编译器和AOT编译器

jdk10开始JIT即时编译器有一个新的编译器 Graal编译器

可以通过参数 -XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler开启该编译器

jdk9开始新增AOT编译器 Ahead Of Time提前编译器

这个编译器是跟JIT对立的,借助了Graal编译器,在程序运行之前就将字节码编译成机器指令.

好处: 提前编译成机器指令,加快了执行的速度,不存在第一次运行慢的问题

坏处:

  1. 提前编译成机器指令,java的跨平台性就受到了影响,因为不同平台的机器指令是不一样的
  2. 降低java的动态性,提前编译,一些只能在java运行中动态生成的特性就被压缩了
  3. 目前只支持Linux  x64 java base
目录
相关文章
|
2月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
77 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
2月前
|
存储 SQL 小程序
JVM知识体系学习五:Java Runtime Data Area and JVM Instruction (java运行时数据区域和java指令(大约200多条,这里就将一些简单的指令和学习))
这篇文章详细介绍了Java虚拟机(JVM)的运行时数据区域和JVM指令集,包括程序计数器、虚拟机栈、本地方法栈、直接内存、方法区和堆,以及栈帧的组成部分和执行流程。
39 2
JVM知识体系学习五:Java Runtime Data Area and JVM Instruction (java运行时数据区域和java指令(大约200多条,这里就将一些简单的指令和学习))
|
2月前
|
Java 应用服务中间件 程序员
JVM知识体系学习八:OOM的案例(承接上篇博文,可以作为面试中的案例)
这篇文章通过多个案例深入探讨了Java虚拟机(JVM)中的内存溢出问题,涵盖了堆内存、方法区、直接内存和栈内存溢出的原因、诊断方法和解决方案,并讨论了不同JDK版本垃圾回收器的变化。
36 4
|
2月前
|
Arthas 监控 Java
JVM知识体系学习七:了解JVM常用命令行参数、GC日志详解、调优三大方面(JVM规划和预调优、优化JVM环境、JVM运行出现的各种问题)、Arthas
这篇文章全面介绍了JVM的命令行参数、GC日志分析以及性能调优的各个方面,包括监控工具使用和实际案例分析。
68 3
|
2月前
|
SQL 缓存 Java
JVM知识体系学习三:class文件初始化过程、硬件层数据一致性(硬件层)、缓存行、指令乱序执行问题、如何保证不乱序(volatile等)
这篇文章详细介绍了JVM中类文件的初始化过程、硬件层面的数据一致性问题、缓存行和伪共享、指令乱序执行问题,以及如何通过`volatile`关键字和`synchronized`关键字来保证数据的有序性和可见性。
35 3
|
2月前
|
缓存 前端开发 Java
JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制
这篇文章详细介绍了JVM中ClassLoader的工作原理,包括类加载器的层次结构、双亲委派机制、类加载过程、自定义类加载器的实现,以及如何打破双亲委派机制来实现热部署等功能。
65 3
|
6月前
|
缓存 Java
《JVM由浅入深学习九】 2024-01-15》JVM由简入深学习提升分(生产项目内存飙升分析)
《JVM由浅入深学习九】 2024-01-15》JVM由简入深学习提升分(生产项目内存飙升分析)
55 0
|
2月前
|
存储 Java
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
这篇文章详细地介绍了Java对象的创建过程、内存布局、对象头的MarkWord、对象的定位方式以及对象的分配策略,并深入探讨了happens-before原则以确保多线程环境下的正确同步。
59 0
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
|
2月前
|
小程序 Oracle Java
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
这篇文章是关于JVM基础知识的介绍,包括JVM的跨平台和跨语言特性、Class文件格式的详细解析,以及如何使用javap和jclasslib工具来分析Class文件。
58 0
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
|
4月前
|
存储 算法 Java
JVM组成结构详解:类加载、运行时数据区、执行引擎与垃圾收集器的协同工作
【8月更文挑战第25天】Java虚拟机(JVM)是Java平台的核心,它使Java程序能在任何支持JVM的平台上运行。JVM包含复杂的结构,如类加载子系统、运行时数据区、执行引擎、本地库接口和垃圾收集器。例如,当运行含有第三方库的程序时,类加载子系统会加载必要的.class文件;运行时数据区管理程序数据,如对象实例存储在堆中;执行引擎执行字节码;本地库接口允许Java调用本地应用程序;垃圾收集器则负责清理不再使用的对象,防止内存泄漏。这些组件协同工作,确保了Java程序的高效运行。
34 3