【JVM第七篇】执行引擎
写在前面的话:本文是在观看尚硅谷JVM教程后,整理的学习笔记。其观看地址如下:尚硅谷2020最新版宋红康JVM教程
执行引擎是Java虚拟机中的核心组成部分。
执行引擎的作用就是解析虚拟机字节码指令,即执行一条条的代码流程,并得到执行结果。
我们可以先来看一下执行引擎在Java虚拟机中的位置,
可以看出,一个Java线程就是一个执行引擎的实例。则在一个JVM实例中就会有很多个执行引擎在工作,可能有的在执行我们编写的应用程序,有的在执行JVM内部程序,如垃圾收集器等。
1、执行引擎的工作过程
“虚拟机”是一个相对于“物理机”的概念,这两种机器都有代码执行功能。其区别是物理机的执行引擎是直接建立在处理器、缓存、指令集和操作系统层面上的,而虚拟机的执行引擎则是由软件自行实现的,因此可以不受物理条件制约,自由地去定制指令集和执行引擎的体系结构,并执行那些不被硬件直接支持的指令集格式。
指令集:所谓指令集,就是在CPU中用来计算和控制计算机系统的一套指令的集合,每一种新型的CPU在设计时都规定了一系列与其他硬件电路相匹配的指令系统。即指令集是用来操纵计算机硬件资的。指令集跟“物理机”上的硬件是绑定的,不同类型的CPU,它的指令集也是不同的。
JVM想要成功运行,也必须跟物理机一样有一套指令集。JVM的指令集就是我们常说的Java字节码。所以,任何符合class文件规范的Java字节码指令都可以被JVM执行。
但字节码并不能够直接运行在操作系统上,因为字节码不等同于本地机器指令(能被机器直接执行的代码)。字节码仅仅包含有能被JVM所识别的字节码指令、符号表,以及其他的辅助信息。
想要让一个Java程序运行起来,执行引擎的作用就是将字节码指令解释(或者编译)成对应平台上的本地机器码。即JVM中的执行引擎充当了将高级语言编译成机器语言的译者。
如图所示,
在JVM中,执行引擎需要执行什么样的字节码指令,完全依赖于程序计数器,程序计数器保存着当前字节码指令的地址。
每执行完一次指令操作后,程序计数器就会更新下一条需要被执行的指令地址。
当然方法在执行过程中,执行引擎有可能会通过存储在局部变量表中的对象引用准确定位到存储在Java堆区中的对象实例信息,以及通过对象头中的元数据指针定位到目标对象的类型。
从宏观上看,所有的Java虚拟机的执行引擎输入输出都是一致的:输入的都是字节码二进制流,处理过程就是字节码解释执行(或编译执行)的过程,输出的都是执行结果。
2、Java代码编译和执行的过程
2.1、编译过程
我们知道,任何一种高级语言,都需要经过翻译才能被实体机器所识别并执行,这个“翻译”的过程就是我们常说的编译,编译器就是专门用于编译工作的。
通常编译器都是将便于人们理解的语言转化为机器能识别的机器码,如C/C++或者汇编语言都是直接将源代码编译成目标机器能识别的二进制机器码。这个机器码也就是CPU能够直接执行的指令集合。
Java语言常用的编译器是Javac,这是JDK自带的一个编译器。但Javac并不是直接将Java源代码编译成机器码,而是编译成JVM能够识别并执行的另一种语言——Java字节码。由一条条字节码组成的文件就是我们常说的字节码文件(.class文件)。
那么字节码指令又如何“翻译”成CPU识别的机器码呢?
这就是JVM中的执行引擎的任务了。
下面我们先简单介绍Java源代码编译成字节码的过程。
一般的流程如下,
词法分析:
将源代码一字节一字节地读入,然后找出这些字节中的语法关键词(如if、for、while等Java定义的保留关键词),其结果就是从源码中找出符合规范的Token(标记)流。就好比给一句话,我们要找出那些是特殊的词语,那些不是。其结果就是找出源码中所有的Java关键字。
语法分析:
检查词法分析中找出来的关键词组合在一起是否符合Java语言规范,如词法分析中找出来一个关键词if,那么语法分析就要检查这个if后面跟着的是不是一个布尔判断表达式,即使用if这个关键词时要符合Java的语法规范。其结果就是形成一个语法树。
语义分析:
即分析语法树,把一些浮渣的语法转化成简单语法。其结果就是使语法更贴近目标语言的语法规划,形成一个注解过后的语法树(比如把foreach转化成for循环,并加上注解)。
字节码生成:
经过以上几步后,Javac会通过其字节码生成器组件生成字节码文件。
Javac的主要模块就是词法分析器、语法分析器、语义分析器和代码生成器。它们共同完成将Java源码转化成字节码的任务。
2.2、执行过程
Java源码经过编译后,会成为Java字节码。而字节码的执行是由JVM中的执行引擎来完成的。
其执行流程如下,
可以看出,JVM的执行引擎在执行字节码时,有通过解释器逐行解释执行和通过JIT编译器编译产生本地代码再执行这两种方式,
什么是解释器和JIT编译器?
解释器(Interpreter):
当JVM启动时会根据预定义的规范对字节码采用逐行解释的方式执行,将每条字节码文件中的内容“翻译”为对应平台的本地机器指令执行。
JIT编译器(Just In Time Complier)
JVM会将字节码直接编译成和本地机器平台相关的机器语言,缓存起来,然后再执行。
在JDK1.0时,JVM采用的是解释执行,后来又发展出了可以直接生成本地代码的即时编译器。
现在JVM在执行Java字节码时,都会将解释执行与编译执行二者结合。
故Java也被称为半编译半解释型语言。
3 、解释器和JIT编译器
3.1、解释器
解释器真正意义上所承担的角色是一个运行时的“翻译者”,将字节码文件中的内容“翻译”为对应平台的本地机器指令执行。
当一条字节码指令被解释执行完成后,接着再根据PC寄存器中记录的下一条需要被执行的字节码指令进行解释执行
在HotSpot虚拟机中,解释器主要由Interpreter模块和Code模块构成。
Interpreter模块:实现解释器的核心功能。
Code模块:用于管理虚拟机在运行时生成的本地机器指令。
由于解释器在设计和实现上非常简单,因此除了Java语言外,还有许多高级语言也同样基于解释器执行,如Pyhton、Perl、Ruby等。但基于解释器执行的效率总是十分低下。
为了解决这一问题,JVM平台支持一种叫做即时编译的技术。即时编译的目的是避免函数被解释执行,而是将整个函数体编译成为机器码。每次函数执行时,只执行编译后的机器码即可,这种方式使执行效率大幅度提升。
3.2、JIT编译器
现代虚拟机为了提高执行效率,会使用即时编译技术将方法编译成机器码后再执行。
HotSpot虚拟机是目前市面上高性能虚拟机的代表作之一。它采用解释器与即时编译器并存的架构,当虚拟机发现某个方法或代码块的运行特别频繁时,就会把这些代码认定为“热点代码”。
为了提高热点代码的执行效率,在运行时,虚拟机会把这些代码编译成与本地平台相关的机器码,并运行各层次的优化,完成这个任务的编译器被称为JIT即时编译器。
3.3、为什么HotSpot要一起使用JIT编译器和解释器?
首先,当程序启动后,解释器可以马上发挥作用,省去编译时间立即执行。而编译器想要发挥作用,得先把代码编译成本地代码,这需要一定的执行时间。所以,如果只使用JIT编译器的话,程序在启动时,必然需要花费更长的时间来编译。而两者共同使用,在程序启动时,解释器立即执行,省去了编译时间。当越来越多的字节码被JIT编译器编译成本地代码后,,可以获得更高的执行效率。
其次,当程序运行环境所能利用的内存资源有限时,可以使用解释器执行以节约内存。若内存充足,则可以使用JIT编译器提升效率。
而且,虽然说JIT编译器比解释器快,但实质上说的是执行编译后的目标代码比边解释边执行快。对于一些只执行一次的代码(如类的初始化方法< clinit >),如果算上编译所需要的时间,实际上解释执行比JIT编译执行要快。只有对于频繁执行的热点代码,JIT编译才能确保编译后执行带来的时间优势能抵消掉编译所需的时间开销。因此,使用解释器执行很少使用或只使用一次的代码,而用JIT编译器执行热点代码,可以在一定程度上保证效率。
同时,当执行JIT编译器编译后的代码出现罕见的错误时,可以通过逆优化的手段,退后到解释状态继续执行。
3. 4、热点代码及探测方式
一个被多次调用的方法,或者是一个方法体内部循环次数达到一定次数的代码被称为热点代码。这些代码会被JIT编译器编译为对应平台的本地机器指令。由于JIT编译发生在方法执行过程中,因此也被称为栈上替换(On Stack Replacement)。
HotSpot虚拟机要如何判断出热点代码?
判断一段代码是否为热点代码的行为被称为“热点探测”,常见的热点探测方式有:
- 基于计数器的探测:JVM会为每个方法(或每个代码块)建立计数器,统计执行次数,如果超过阀值那么就是热点代码。缺点是维护计数器开销。
- 基于采样的探测:JVM会周期性检查各个线程的虚拟机栈的栈顶,如果某个方法经常出现在栈顶,那么就是热点代码。缺点是不精确。
HotSpot采用的热点探测方式是基于计数器的热点探测。HotSpot虚拟机会为每个方法都建立两个不同类型的计数器,分别为方法调用计数器(Invocation Counter) 和 回边计数器(Back Edge Counter)。
方法计数器用于统计方法的调用次数。回边计数器则用于统计循环体执行的循环次数。
3.5、方法调用计数器
这个计数器就用于统计方法被调用的次数,它的默认阈值在Client模式下是1500次,在Server模式下是10000次。超过这个阈值,就会触发JIT编译。
可以通过虚拟机参数 -XX:Complier Threshold 来设定阈值。
当一个方法被调用时,会检查该方法是否存在被JIT编译过的版本。如果存在,则优先使用编译后的本地代码来执行。如果不存在已被编译的版本,则将此方法的调用计数器的值加1,然后判断方法调用计数器与回边计数器之和是否超过方法调用计数器的阈值。如果已超过阈值,则会向即时编译器提交一个该方法的代码编译请求。
如果不做任何设置,方法调用计数器统计的并不是方法的绝对调用次数,而是一段时间内的方法被调用的次数。当超过一定的时间限度,如果方法的调用次数仍然不足以让它提交给即时编译器编译,那么这个方法的调用计数器值就会减半,这个过程称为方法调用计数器的热度衰减(Counter Decay),而这段时间被称为方法的半衰周期( Counter Half Life Time )。
进行热度衰减的动作是在虚拟机进行垃圾回收时一起进行的,可以使用虚拟机参数来关闭热度衰减,让方法计数器统计方法的绝对调用次数。这样,只要系统运行的时间足够长,绝大部分方法都会被编译成本地代码。当然也可以使用虚拟机参数来设置半衰期的时间,单位是秒。
-XX:+/-UseCounterDecay
开启/关闭热度衰减
-XX:CounterHalfLifeTime
设置半衰期时间,单位秒
3.6、回边计数器
回边计数器的作用是统计方法中循环体代码执行的次数,在字节码中遇到控制流向后跳转的指令称为回边(Back Edge)。
显然,建立回边计数器的目的就是为了触发OSR编译。
3.7、HotSpot设置程序执行方式
在缺省情况下(即未设置参数时)HotSpot虚拟机是采用解释器与即时编译器并存的架构。
开发人员也可以根据具体的应用场景,通过命令显式地为JVM指定在运行时采用的是完全解释执行,或者是完全采用编译执行,亦或是混合执行(默认)。
虚拟机设置的参数如下,
-Xint
: 完全采用解释器模式执行程序。
-Xcomp
: 完全采用即时编译器模式执行程序(如果编译器出现问题,解释器就会介入)。
-Xmixed
: 采用解释器加即时编译器的混合模式共同执行程序。
3.8、C1编译器和C2编译器
在HotSpot虚拟机中内置了两个JIT编译器,分别为Client Compiler和Server Compiler,我们平时简称为C1编译器和C2编译器。
C1编译器会对字节码进行简单和可靠的优化,耗时短,以达到更快的编译速度。C2编译器进行耗时较长的优化,属激进优化,但字节码码执行效率更高。
可以通过以下虚拟机参数指定Java虚拟机运行时使用的是哪一个即时编译器。
-Client
:指定JVM运行在Client模式下,并使用C1编译器
-Server
:指定JVM运行在Server模式下,并使用C2编译器
JVM的Client模式和Server模式:
JVM的Server模式启动时,速度较慢,但当运行起来后,性能将会比Client模式更高。原因是JVM在Client模式下使用的是C1编译器(体量较轻)。当JVM运行在Server模式下时,使用的是C2编译器(体量较重)。C2编译器比C1编译器更加彻底,服务运行起来后性能更强,但启动时间长。
我们可以直接在命令行输入 java -verion
来查看当前JVM是运行在何种模式下。
如果我们在JVM启动时,没有指定运行在那种模式下,那么虚拟机将会自动检测主机是否为服务器。如果是,则以Server模式运行,否则以Client模式运行。虚拟机检测是否为服务器的标准是,至少有两个CPU和最低2G内存。
不过需要注意的是,如果想切换模式,要先确认JDK是否两种模式都支持。
查看方式是,去JAVA_HOME/jre/bin
目录下看是否存在名为Client和Server的目录,存在即表明支持。一般情况下,32位的机器都支持Server模式和Client模式。但64位的JDK只支持Server模式。
C1和C2编译器不同的优化策略:
在不同的编译器上有不同的优化策略。
(1)、C1编译器上主要有方法内联、去虚拟化、冗余消除
①方法内联:将引用的函数代码编译到引用处,这样可以减少栈帧的生成,减少参数传递以及跳转过程。
②去虚拟化:对唯一的实现类进行内联。
③冗余消除:在运行期间把一些不会执行的代码折叠掉
(2)、C2的优化主要在全局层面,逃逸分析是优化的基础。基于逃逸分析在C2上有如下几种优化:
①标量替换:用标量值替换聚合对象的属性值
②栈上分配:对于未发生逃逸的对象分配内存在栈上而不是堆上。
③同步消除:消除同步操作,通常指消除syhchronized。
分层编译策略:
第一层,为解释执行,在程序解释执行(不开启性能监控)时,可触发第二层。第二层,触发C1编译,可以进行简单优化。第三层,加上性能监控,C2编译器会根据性能监控信息进行激进优化。
不过在现在的Java7版本后,如在启动时指定运行Server模式时,默认将开启分层编译策略,由C1和C2相互协作共同编译任务。
总结:
一般来讲,JIT编译器编译出来的机器码性能比解释器高。C2编译器启动时间比C1编译器长,但C2编译器执行速度远快于C1编译器。
扩展:
JDK9中引入了AOT编译器(静态提前编译器,Ahead Of Time Compiler)
所谓AOT编译,是与即时编译器相对应的概念。在即时编译器中,字节码在程序运行时被转换为可在硬件上执行的本地机器码。而AOT编译,则是在程序执行之前,就直接将字节码编译为本地机器码,在JVM启动时即立即执行。
这样做的好处是:
JVM可以直接执行这些被AOT编译过的代码,无需等待即时编译器的预热,加快了启动速度。
缺点是:
①破坏了Java一次编译,到处运行的特性,必须为每个不同硬件,操作系统编译对应的发行包。
②降低了Java链接过程的动态性,加载的代码在编译期必须全部已知,否则将无法编译通过动态生成class文件的Java代码(如反射等)。
③目前也仅仅支持64的Linux操作系统。
【JVM第七篇】执行引擎的更多相关文章
- Java之深入JVM(6) - 字节码执行引擎(转)
本文为转载,来自 前面我们不止一次的提到,Java是一种跨平台的语言,为什么可以跨平台,因为我们编译的结果是中间代码—字节码,而不是机器码,那字节码在整个Java平台扮演着什么样的角色的呢?JDK1. ...
- JVM(6) 字节码执行引擎
编译器(javac)将Java源文件(.java文件)编译成Java字节码(.class文件). 类加载器负责加载编译后的字节码,并加载到运行时数据区(Runtime Data Area) 通过类加载 ...
- JVM之字节码执行引擎
方法调用: 方法调用不同于方法执行,方法调用阶段唯一任务就是确定被调用方法的版本(即调用哪一个方法),暂时还不执行方法内部的具体过程.方法调用有,解析调用,分派调用(有静态分派,动态分派). 方法解析 ...
- 一夜搞懂 | JVM 字节码执行引擎
前言 本文已经收录到我的 Github 个人博客,欢迎大佬们光临寒舍: 我的 GIthub 博客 学习导图 一.为什么要学习字节码执行引擎? 代码编译的结果从本地机器码转变为字节码,是存储格式发展的一 ...
- 【JVM之内存与垃圾回收篇】执行引擎
执行引擎 执行引擎概述 执行引擎属于 JVM 的下层,里面包括 解释器.及时编译器.垃圾回收器 执行引擎是 Java 虚拟机核心的组成部分之一. "虚拟机"是一个相对于" ...
- 执行引擎子系统——JVM之五
一.JVM通过执行引擎来完成字节码的执行,在执行过程中JVM采用的是自己的一套指令系统,每个线程在创建后,都会产生一个程序计数器(pc)和栈(Stack). pc:存放了下一条将要执行的指令: Sta ...
- 深入理解JVM虚拟机5:虚拟机字节码执行引擎
虚拟机字节码执行引擎 转自https://juejin.im/post/5abc97ff518825556a727e66 所谓的「虚拟机字节码执行引擎」其实就是 JVM 根据 Class 文件中给 ...
- JVM学习笔记:字节码执行引擎
JVM学习笔记:字节码执行引擎 移步大神贴:http://rednaxelafx.iteye.com/blog/492667
- 深入理解JVM—字节码执行引擎
原文地址:http://yhjhappy234.blog.163.com/blog/static/3163283220122204355694/ 前面我们不止一次的提到,Java是一种跨平台的语言,为 ...
随机推荐
- Go语言基础知识01-用Go打个招呼
每一种编程语言,从读一本好书开始 每一种编程语言,也从Helloworld开始 1. 环境准备 1.1 安装golang 在Ubuntu下,直接输入命令可以安装最新版本: $ sudo apt-get ...
- centos8用firewalld搭建防火墙
一,firewalld的systemd管理命令 启动:systemctl start firewalld 关闭:systemctl stop firewalld 查看状态:systemctl stat ...
- centos8上安装openresty
一,openresty的官网地址: http://openresty.org/ 说明:说一下openresty的安装方式: 从openresty的安装目录下,可以看到openresty编译安装了自己作 ...
- 赋予楼宇“智慧大脑”:厦门双子塔3D可视化
前言 今年10月7日,是国务院批准设立厦门经济特区40周年纪念日.1980年的这一天,国务院正式批复同意在厦门湖里地区划出一块2.5平方公里的土地,设立经济特区.厦门,成为中国最早设立的四个经济特区之 ...
- Python之dict字典详解
Python内置了字典:dict的支持,dict全称dictionary,在其他语言中也称为map,字典是另 一种可变容器模型,且可存储任意类型对象.具有极快的查找速度. 字典是一种通过名字或者关键字 ...
- 彩贝网app破解登入参数(涉及app脱壳,反编译java层,so层动态注册,反编译so层)
一.涉及知识点 app脱壳 java层 so层动态注册 二.抓包信息 POST /user/login.html HTTP/1.1 x-app-session: 1603177116420 x-app ...
- 1-1Java概述
001_Java语言发展史 Sun公司:Stanford University Network 002Java跨平台原理 平台:指的是操作系统Windows,Mac,Linux等. 总结:在需要运行 ...
- linux修改环境变量后无法登录
在登陆界面按Ctrl+Alt+F1(F1~F6), 进入 tty 后登陆账号. 执行以下命令: /usr/bin/sudo /usr/bin/vi /etc/environment 将PATH的值复原 ...
- Linux入门到放弃之八《任务计划管理》
任务计划管理 1.每周一下午5:50将/data目录下的所有目录和文件归档并压缩为:backup.tar.gz 放在/home/backup目录下. 先新建/data目录,并在目录中随意生成几个文件 ...
- Kubernetes K8S之调度器kube-scheduler详解
Kubernetes K8S之调度器kube-scheduler概述与详解 kube-scheduler调度概述 在 Kubernetes 中,调度是指将 Pod 放置到合适的 Node 节点上,然后 ...