java虚拟机字节码执行引擎
定义
java虚拟机字节码执行引擎是jvm最核心的组成部分之一,它做的事情很简单:输入的是字节码文件,处理过程是字节码解析的等效过程,输出的是执行结果。在不同的虚拟机实现里,执行引擎在执行java代码的时候可能会有解释执行和编译执行两种选择,也可能两者兼备。
运行时栈帧结构
java字节码执行引擎在调用和执行方法的时候使用了一种叫做栈帧的数据结构。
在jvm的内存结构里,存在这一块称为虚拟机栈的内存区域,虚拟机栈中的元素就是栈帧。每个方法的调用至结束对应着一个栈帧在虚拟机栈的入栈和出栈。
栈帧数据结构示意图:
如图所示,因为虚拟机栈是线程私有的,所以每个线程都有自己的虚拟机栈;而每个线程的虚拟机栈中都有多个栈帧对应一个方法调用链中的多个方法,栈顶的栈帧是当前正在执行的方法;每个栈帧主要包含4个部分:
- 局部变量表
- 操作数栈
- 动态连接
- 方法返回地址
在编译代码的阶段,栈帧中需要多大的局部变量表,多深的操作数栈都是已经完全确定的,而且会写入到方法表的Code属性中。
接下来一次介绍栈帧中的4个部分:
局部变量表
局部变量表是一组变量值存储空间,用于存放方法参数和方法内局部变量。
局部变量表的容量以变量槽Slot为最小单位,规定一个Slot可以存放一个32位以内的数据类型,java中占32位以内的数据类型有8种基本数据除了long和double以外的6种,还有reference和returnAddress,共8种类型。而对于64位的数据类型,即long和double,则会以高位对齐的方式为其分配两个连续的Slot空间。
在方法执行时,方法的参数列表是通过局部变量表传递的,如果执行的是实例方法(非static方法),则局部变量表中的第0位索引的Slot默认保存方法所属对象的引用,以支持“this”关键字来访问对象数据。其余参数则按参数列表顺序,占用从1开始的局部变量Slot,参数表分配完成后,在根据方法体内局部变量定义的顺序和作用域为局部变量分配其余Slot。之所以这里要强调作用域是因为为了节省栈帧空间,局部变量表中的Slot是可以重用的,当一个Slot中保存的局部变量超出其作用域后,这个Slot可以被复用来存储新的变量。
操作数栈
操作数栈是用来执行字节码命令的,比如iadd命令在运行的时候,就是把操作数栈中最接近栈顶的2个元素出栈相加,然后将结果入栈。
操作数栈的每一个元素可以是任意的java数据类型,32位数据所占栈容量为1,64位数据所占栈容量为2。
java虚拟机的解释执行引擎是“基于栈的执行引擎”,这里的栈就是指操作数栈。
动态连接
一个指向运行时常量池的引用,用来支持当前方法的代码实现动态链接。
方法返回地址
一个方法开始后,只有两种方式可以退出,一种是当执行引擎遇到任意一个返回字节码指令的时候,另一种是在执行中遇到异常并且没有捕获的时候。无论以哪种方式退出,退出后都需要返回到方法被调用的位置,因此需要在栈帧中保存一些信息,用来恢复它上层方法的执行状态。
方法调用
方法调用不等于方法执行,方法调用阶段的唯一目的就是确定被调用方法的版本。
解析
我们知道,在虚拟机进行类加载的时候,有一个阶段叫做“解析”,在解析阶段会将常量池中的一部分符号引用转化为直接引用,在这个阶段,有一部分方法调用就已经被解析为了直接引用,解析的前提是,这部分方法的调用目标在编译阶段就可以确定下来。
在java虚拟机中共有5个方法调用指令:
- invokestatic
- invokespecial
- invokevirtual
- invokeinterface
- invokedynamic
可以在“解析”阶段就确认调用目标的有invokestatic和invokespecial指令调用的方法以及invokevirtual调用的final方法,这些方法被称为“非虚方法”,与之相反的被称为“虚方法”。
分派
分派分为“静态分派”和“动态分派”。
静态分派
静态分派用来实现java语言的"重载"特性。
当我们声明一个变量时,可能会用到这种形式:Human man=new Man();(Man是Human子类),对于man这个变量来说,Human叫做它的静态类型,Man叫做它的实际类型。
"重载"是基于静态类型。也就是说,对于相同名称,参数列表不同的方法,选择哪个方法取决于参数列表中参数的静态类型,而静态类型在编译时是可知的,因此静态分派发生在编译阶段。
动态分派
动态分派用来实现java语言的“重写”特性,动态分派对应invokevirtual命令。
invokevirtual命令的执行过程如下:
- 找到操作数栈栈顶的第一个元素所指向的对象(也被称为方法的接收者)实际类型,记为C。
- 如果在C中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限验证,如果通过则返回这个方法的直接引用,查找结束;如果不通过,则返回java.lang.IllegalAccessError异常。
- 否则,按照继承关系从下往上依次对C的各个父类进行第2步的搜索和验证过程。
- 如果最终没有找到合适的方法,则抛出java.lang.AbstractMethodError异常。
我们可以看出invokevirtual指令执行的时候是由对象的实际类型决定调用哪个方法版本的,而对象的实际类型只有到运行期的时候才能确定,我们把这种在运行期确定方法版本的分派过程称为“动态分派”。
单分派和多分派
方法的接收者与方法的参数统称为方法的宗量,根据分派基于宗量的多少,将分派分为单分派和多分派两种。
目前的java语言中,静态分派的时候,是基于接收者和方法参数两个宗量进行的,因此静态分派是多分派;动态分派的时候,是基于接收者一个宗量进行的,因此动态分派是单分派。
方法执行
一般从程序代码到物理机可以运行的目标代码都要经历下图所示的过程:
图中展示了编译执行和解释执行这两种路径,无论是哪种执行方式,一般都会先通过词法分析和语法分析把源代码转化为抽象语法树(AST)。
然后从抽象语法树节点开始,中间的分支代表的是解释执行过程,下边的分支代表的是编译执行过程。
jvm执行字节码是采用的解释执行,javac编译器完成了程序代码经过词法分析、语法分析到抽象语法树,再遍历语法树生成线性的字节码指令流的过程。而解释器则在虚拟机的内部。
javac编译器输出的字节码指令流是一种基于栈的指令集架构,指令流中的大部分指令都是零地址指令,它们依赖操作数栈进行工作。与之相对的是基于寄存器的指令集。
基于栈的指令集有以下优点:
- 可移植
- 代码更加紧凑(不需要存放参数)
- 编译器实现更简单(不需要考虑空间分配问题,所需空间都在栈上操作)
基于栈的指令集的主要缺点就是执行速度慢:一个是因为基于栈完成相同功能所需要的指令集数量一般比基于寄存器要多,因为入栈、出栈会产生多余的指令数量;另一个是因为栈是基于内存实现的,内存的读取速度本身就比处理器寄存器要慢,这一点可以采取栈顶缓存的手段,把最常用的操作映射到寄存器中避免直接访问内存,但是毕竟只是优化措施,不能解决本质问题。
执行方法中的代码本质就是通过栈的指令集来进行运算,这里就不详述了。
java虚拟机字节码执行引擎的更多相关文章
- 【java虚拟机系列】从java虚拟机字节码执行引擎的执行过程来彻底理解java的多态性
我们知道面向对象语言的三大特点之一就是多态性,而java作为一种面向对象的语言,自然也满足多态性,我们也知道java中的多态包括重载与重写,我们也知道在C++中动态多态是通过虚函数来实现的,而虚函数是 ...
- Java虚拟机-字节码执行引擎
概述 Java虚拟机规范中制定了虚拟机字节码执行引擎的概念模型,成为各种虚拟机执行引擎的统一外观(Facade).不同的虚拟机引擎会包含两种执行模式,解释执行和编译执行. 运行时帧栈结构 栈帧(Sta ...
- 《深入理解Java虚拟机》-----第8章 虚拟机字节码执行引擎——Java高级开发必须懂的
概述 执行引擎是Java虚拟机最核心的组成部分之一.“虚拟机”是一个相对于“物理机”的概念 ,这两种机器都有代码执行能力,其区别是物理机的执行引擎是直接建立在处理器.硬件.指令集和操作系统层面上的,而 ...
- Java虚拟机--虚拟机字节码执行引擎
Java虚拟机--虚拟机字节码执行引擎 所有的Java虚拟机的执行引擎都是一致的:输入的是字节码文件,处理过程是字节码解析的等效过程,输出的是执行结果. 运行时栈帧结构 用于支持虚拟机进行方法调用和方 ...
- JAVA虚拟机:虚拟机字节码执行引擎
“虚拟机”是一个相对“物理机”的概念,这两种机器都有代码执行能力. 物理机的执行引擎是直接建立在处理器.硬件.指令集和操作系统层面上的. 虚拟机的执行引擎由自己实现,自行制定指令集与执行引擎的结构体系 ...
- 深入理解JVM虚拟机5:虚拟机字节码执行引擎
虚拟机字节码执行引擎 转自https://juejin.im/post/5abc97ff518825556a727e66 所谓的「虚拟机字节码执行引擎」其实就是 JVM 根据 Class 文件中给 ...
- 深入了解java虚拟机(JVM) 第十三章 虚拟机字节码执行引擎
一.概述 执行引擎是java虚拟机最核心的组成部件之一.虚拟机的执行引擎由自己实现,所以可以自行定制指令集与执行引擎的结构体系,并且能够执行那些不被硬件直接支持的指令集格式.所有的Java虚拟机的执行 ...
- 【Java】JVM(六)虚拟机字节码执行引擎
一.概述 执行引擎是虚拟机中最核心的部分之一, 虚拟机自己实现引擎,自己定义指令集和执行引擎的结构体系. 二.栈帧 栈帧包含(1)局部变量表.(2)操作数栈.(3)动态链接.(4)方法返回地址.(5) ...
- 《java虚拟机》----虚拟机字节码执行引擎
No1: 物理机的执行引擎是直接建立在处理器.硬件.指令集合操作系统层面上的,而虚拟机的执行引擎则是由自己实现的,因此可以自行制定指令集与执行引擎的结构体系,并且能够执行那些不被硬件直接支持的指令集格 ...
随机推荐
- Codevs1169:传纸条——题解
题目描述 Description 小渊和小轩是好朋友也是同班同学,他们在一起总有谈不完的话题.一次素质拓展活动中,班上同学安排做成一个m行n列的矩阵,而小渊和小轩被安排在矩阵对角线的两端,因此,他们就 ...
- 【转】TCP拥塞控制,慢启动、拥塞避免、快重传以及快恢复
转自:http://blog.csdn.net/yusiguyuan/article/details/22847787 注:本文绝大部分是来自转载的博客,还补充了少量内容. 一.TCP的拥塞控制 拥塞 ...
- CCPC-Winter Camp div2 day8 A
---恢复内容开始--- 题目: 题解: 我们考虑第i个叶子节点的情况,根据题目给的输入条件,我们可以知道,深度大的节点的序号一定是大于深度浅的节点的序号的 如图 题目要求我们找出每一个叶子节点距离编 ...
- stout代码分析之十一:hashmap和multihashmap
hashmap是std::unordered_map的子类,前者对后者的接口做了进一步封装. hashmap的移动构造函数: hashmap(std::map<Key, Value>&am ...
- bzoj 2434 [Noi2011]阿狸的打字机 AC自动机
[Noi2011]阿狸的打字机 Time Limit: 10 Sec Memory Limit: 256 MBSubmit: 4001 Solved: 2198[Submit][Status][D ...
- 单例 ------ JAVA实现
单例:只能实例化一个对象,使用场景比如打印机. 最推荐的是采用饿汉式:双重校验锁用到了大量的语法,不能保证这些语法在所用场合一定没问题,所以不是很推荐:总之简单的才是最好的,就饿汉式!!! C++ 创 ...
- VUE 内置的标签<keep-alive></keep-alive>作用
<keep-alive></keep-alive> 的作用是 包裹动态组件时,会缓存不活动的组件实例,主要用于保留组件状态或避免重新渲染组件
- 【C++对象模型】第五章 构造、解构、拷贝 语意学
1.构造语义学 C++的构造函数可能内带大量的隐藏码,因为编译器会扩充每一个构造函数,扩充程度视 class 的继承体系而定.一般而言编译器所做的扩充操作大约如下: 所有虚基类成员构造函数必须被调用, ...
- 「模板」 树链剖分 HLD
「模板」 树链剖分 HLD 不懂OOP的OIer乱用OOP出人命了. 谨此纪念人生第一次类套类. 以及第一次OI相关代码打过200行. #include <algorithm> #incl ...
- POJ 3069 Saruman's Army (模拟)
题目连接 Description Saruman the White must lead his army along a straight path from Isengard to Helm's ...