深入了解jvm-2Edition-虚拟机字节码执行引擎
1、概述
Java虚拟机规范制定了虚拟机字节码执行引擎的概念模型,本章主要从概念模型层次来探究虚拟机的方法调用和字节码执行。
方法调用中,最核心的,是如何确定调用的方法,也就是方法的分派。
字节码执行过程中,特别重要的一点是执行上下文的切换和信息的交换处理。这需要运行时数据结构的支持,也就是运行时栈帧。
2、运行时栈帧结构
运行时栈帧(Stack Frame)是用于支持虚拟机方法调用和方法执行的数据结构。
它是虚拟机运行时数据区中的虚拟机栈的栈元素。
存储了方法的局部变量表、操作数栈、动态链接和方法返回地址等信息。
方法的调用、执行、返回过程就是栈帧在栈里入栈(创建)、内部信息改变、出栈(销毁)的过程。
在编译过程中,栈帧中的局部变量表的大小、操作数栈的深度就已经确定并记录在了方法的code属性里面了。
对于执行引擎来说,只有栈顶的栈帧(当前栈帧,对应当前方法)是有效的。
1、局部变量表
存放方法参数和方法内部定义的局部变量。
容量以槽(Slot)为最小单位。
虚拟机规范没有规定槽的大小,
只说了每个槽都能存放一个boolean、byte、char、short、int、float、reference、或 returnAddress数据类型。
因此可以说一个Slot可以存放一个32位及以下的数据类型。
64位的数据类型要占用两个Slot(long、double),高位对齐。
reference数据至少要能帮助虚拟机完成两项功能:
1、直接或间接地查找到对象在Java堆中的起始地址;
2、直接或间接地在方法区中查找到对象所属数据类型(对象的元数据)。
局部变量列表中,索引从0开始,第0位存放的是方法隐含的参数this(非static方法)。
其余位置先按参数列表的顺序存放参数,再按局部变量定义的顺序存放局部变量。
局部变量表中的引用会影响到GC的行为,因为它是GC Roots之一。
如果局部变量表中的引用还存在,那么GC就不会清除引用指向的对象。
将对象引用置为null来帮助GC的原理就是手动将局部变量表中对应的的Slot清空。
置null操作意义不大,这通常会被编译器优化掉。。。
最重要的一点!局部变量表不像方法区中的类一样有初始化赋值过程(准备阶段),
因此,没有赋初始值的局部变量是不能使用的。不像类变量一样有系统初始值。
2、操作数栈
操作数栈是方法执行的最基础的支撑。
操作数栈中元素的数据类型要与字节码指令严格匹配,这在编译时会保证,在类校验阶段还要再次验证。
3、动态链接
指向方法区中运行时常量池中该栈帧所属方法的引用,为了支持方法调用过程中的动态链接。
静态解析:在类加载或第一次使用的时候就将符号引用转换为直接引用。
动态链接:在运行期间才转转为直接引用。
4、方法返回地址
正常完成出口:方法正常执行退出
异常完成出口:。。。
方法退出过程就是将当前栈帧出栈,并恢复上层方法的局部变量表和操作数栈,
把返回值压入上层方法的操作数栈中,调整PC的值,指向下一条指令。
5、附加信息
调试信息等。
3、方法调用
方法调用不等同于执行,调用只是确定是哪一个方法(参数、返回值、所属类)。
1、解析
调用目标在编译期就确定,这就是解析调用。
方法能解析的前提:方法在程序运行前就有一个可确定的调用版本,并且该版本在运行期不变。
符合该前提的方法主要包括静态方法和私有方法。
静态方法直接和类关联,私有方法不可访问,因此它们都不可通过继承或其他方式重写。
虚拟机中的方法调用指令:
1、invokespecial:调用构造器<init>,私有方法和父类方法。
2、invokestatic:调用静态方法。
3、invokevritual:调用虚方法
4、invokeinterface:调用接口方法
5、invokedynamic:动态解析调用方法。
只要能够被1、2调用的方法都可以在解析时确定。
4、方法调用-分派
解析调用在编译期完成,是静态的。
分派则可以是静态的也可以是动态的。
按照宗量数又可分为单分派和多分派。(方法接收者与参数统称为方法宗量)
因此,就可组合出:动/静态单/多分派 四种分派方式。
静态分派是重载的虚拟机层面的实现。动态分派是重写的虚拟机层面的实现。
1、静态分派
Human man = new Man();
其中,Human称为变量的静态类型(Apparent Type),Man称为变更量的实际类型(Actual Type)。
静态类型在编译时就可以确定,但是实际类型要在运行时才能确定。
其实,从英文名就很好理解,Apparent Type就是表面上的类型,Actual Type就是实际上的类型。
对于man,在编译时就可以确定它是一个Human类型,但是,他到底是Man还是Woman要等程序运行时才知道。
方法被重载时,是通过静态类型作为方法的选择依据的,因此在编译时就可以选定重载方法。
依据静态类型来定位方法的执行版本的分派就称为静态分派。
所以,静态分派不是虚拟机做的,它是编译期做的。
2、动态分派
既然静态分派是在编译期,那么动态分派就在运行期咯。
void sayHello(Human human){ human.hello(); }
sayHello(man);
sayHello(woman);
对于上述代码,怎么去确定human.hello()要调用的方法呢?
javap 反编译后,发现它们都是由invokevirtual调用的,但是,两个invokevirtual都是指向的Human的hello()。
但是两个执行的方法明显是不同的。
这就是因为invokevirtual指令的多态查找过程:
1、找到操作数栈栈顶的元素指向的对象的实际类型,记为C。
都找到实际类型了,多态不就解决了。
2、在C中查找与invokevirtual指令参数常量描述符和简单名都相符的方法,
找到后,要检查访问权限,权限不通过,则抛出IllegalAccessError异常。
3、否则,到继承链上寻找。
4、否则,抛出AbstractMethodError异常。
可以看出,invokevirtual指令的执行结果是和操作数栈的状态相关的,
还可以看出,调用对象方法时,首先要做的,就是将对象引用入栈。
因此就多态就实现了。
3、单分派和多分派
方法的接收者与方法的参数统称为方法的宗量。根据分派基于多少宗量,可以将分派划分为单分派和多分派。
上面代码中,对 father.Chioce(new Candy());处代码 编译期选择依据两点:
注意father的类型是可编译时确定的。因此为静态分派。
1、静态类型是Father还是Son;
2、方法参数是Candy还是Fist。
基于两个宗量进行的,因此静态分派属于多分派类型。
对son.Choice(new Candy()); 处调用:
son的类型在编译期无法确定,因此为动态分派。
但是,此时编译器已经指定了方法的参数必须是Candy类型的。
因此,动态分派时只需要确定方法的所属类。
因此,Java的动态分派属于单分派类型。
Java是静态多分派,动态单分派的类型。
4、虚拟机动态分派实现
出于性能考虑,在实现中,为类在方法区中建立了一个虚方法表(Virtual Method Table),
用于invokevirtual指令执行时,直接在该虚方法表中查找方法。
虚方法表中存放着各个方法的实际入口地址,
如果子类没有重写父类方法,那么子类的虚方法表中,该方法指向父类方法的实现入口。
如果子类重写了,就指向子类自己的实现的入口。
为了实现方便,相同签名的方法在子类和父类虚方法表中的索引都一样。
虚方法表一般在类加载的链接阶段初始化,就是在类第一次初始化之后。
为了invokeinterface执行,也建立了接口方法表(Interface Method Table)。
5、动态类型语言支持
动态类型语言可以实现在运行时自由地为类绑定字段和方法,这就要求,在进行方法分派时,可以有自己的选择。
但是目前讲到的分派,方法分派时的查找都是规定好了的。
因此,要支持动态类型支持,就要将方法分派的接口分享出来,让我们可以自己去进行分派。
jdk1.7引入了java.lang.invoke包,提供了一种新的动态确定目标方法的机制:
MethodHandle
A method handle is a typed, directly executable reference to an underlying method, constructor, field,
or similar low-level operation, with optional transformations of arguments or return values.
也就是说,除了只能把类作为单独实体来使用,我们可以通过MethodHandle将方法也抽象成一个单独实体。
(虽然也是通过类来实现的。。。)
好了,我们现在能单独使用方法了,但是,还得找到它吧。
这就涉及到怎么确定一个方法:
1、方法所属类
2、方法简单名
3、方法描述符(参数,返回值)
MethodType
A method type represents the arguments and return type accepted and returned by a method handle,
or the arguments and return type passed and expected by a method handle caller.
MethodType封装了对方法描述符的表示。
现在:
1、类可以用类的Class对象表示;
2、方法简单名——字符串
3、方法描述符——MethodType
就可以去找方法了。
MethodHandles类为我们提供了许多根据上述标识找方法的封装。太贴心了。
invokedynamic指令:
同MethodHandle机制一样,只是MethodHandle是上层实现,invokedynamic是底层实现。
每一处invokedynamic指令的位置都被称作动态调用点(Dynamic Call Site)。
CallSite:
A CallSite
is a holder for a variable MethodHandle, which is called its target
.
An invokedynamic
instruction linked to a CallSite
delegates all calls to the site's current target.
invokedynamic指令的第一个参数不是CONSTANT_Methodref_info常量,
而是新增的CONSTANT_InvokeDynamic_info。
CONSTANT_InvokeDynamic_info包含三个信息:
1、引导方法;
2、方法类型MethodType
3、方法名称
根据前面分析,方法名称、描述符有了,但是还差方法所属类。所以,引导方法中,应该要提供查找类!
引导方法(Bootstrap Method):
存放在BootstrapMethods属性中,是有固定参数,并且返回值是java.lang.invoke.CallSite对象的方法。
代表真正要执行的目标方法调用。
根据CONSTANT_InvokeDynamic_info中的信息,虚拟机找到并执行引导方法,得到一个CallSite对象,
最终使用CallSite调用目标方法。
现在有了方法的标识,谁去帮我们找呢?
MethodHandles.Lookup lookup() :
Returns a Lookup object with full capabilities to emulate all supported bytecode behaviors of the caller.
Lookup对象可以模拟调用的字节码行为。就是它了。
6、 基于栈的字节码解释执行引擎
主要注意,基于操作数栈,数据交换都要经过操作数栈。指令也是针对栈元素进行操作的。
深入了解jvm-2Edition-虚拟机字节码执行引擎的更多相关文章
- 【JVM】虚拟机字节码执行引擎
概念模型上,典型的帧栈结构如下(栈是线程私有的,也就是每个线程都会有自己的栈). 典型的帧栈结构 局部变量表 存放方法参数和方法内部定义的局部变量.在编译阶段, ...
- 深入理解JVM虚拟机5:虚拟机字节码执行引擎
虚拟机字节码执行引擎 转自https://juejin.im/post/5abc97ff518825556a727e66 所谓的「虚拟机字节码执行引擎」其实就是 JVM 根据 Class 文件中给 ...
- 【java虚拟机系列】从java虚拟机字节码执行引擎的执行过程来彻底理解java的多态性
我们知道面向对象语言的三大特点之一就是多态性,而java作为一种面向对象的语言,自然也满足多态性,我们也知道java中的多态包括重载与重写,我们也知道在C++中动态多态是通过虚函数来实现的,而虚函数是 ...
- java虚拟机字节码执行引擎
定义 java虚拟机字节码执行引擎是jvm最核心的组成部分之一,它做的事情很简单:输入的是字节码文件,处理过程是字节码解析的等效过程,输出的是执行结果.在不同的虚拟机实现里,执行引擎在执行java代码 ...
- 《深入理解Java虚拟机》-----第8章 虚拟机字节码执行引擎——Java高级开发必须懂的
概述 执行引擎是Java虚拟机最核心的组成部分之一.“虚拟机”是一个相对于“物理机”的概念 ,这两种机器都有代码执行能力,其区别是物理机的执行引擎是直接建立在处理器.硬件.指令集和操作系统层面上的,而 ...
- Java虚拟机--虚拟机字节码执行引擎
Java虚拟机--虚拟机字节码执行引擎 所有的Java虚拟机的执行引擎都是一致的:输入的是字节码文件,处理过程是字节码解析的等效过程,输出的是执行结果. 运行时栈帧结构 用于支持虚拟机进行方法调用和方 ...
- Java虚拟机-字节码执行引擎
概述 Java虚拟机规范中制定了虚拟机字节码执行引擎的概念模型,成为各种虚拟机执行引擎的统一外观(Facade).不同的虚拟机引擎会包含两种执行模式,解释执行和编译执行. 运行时帧栈结构 栈帧(Sta ...
- JAVA虚拟机:虚拟机字节码执行引擎
“虚拟机”是一个相对“物理机”的概念,这两种机器都有代码执行能力. 物理机的执行引擎是直接建立在处理器.硬件.指令集和操作系统层面上的. 虚拟机的执行引擎由自己实现,自行制定指令集与执行引擎的结构体系 ...
- JVM基础结构与字节码执行引擎
JVM基础结构 JVM内部结构如下:栈.堆. 栈 JVM中的栈主要是指线程里面的栈,里面有方法栈.native方法栈.PC寄存器等等:每个方法栈是由栈帧组成的:每个栈帧是由局部变量表.操作数栈等组成. ...
- 深入了解java虚拟机(JVM) 第十三章 虚拟机字节码执行引擎
一.概述 执行引擎是java虚拟机最核心的组成部件之一.虚拟机的执行引擎由自己实现,所以可以自行定制指令集与执行引擎的结构体系,并且能够执行那些不被硬件直接支持的指令集格式.所有的Java虚拟机的执行 ...
随机推荐
- 详细解释 使用FileReference类加载和保存本地文件
一般而言,用户不希望web浏览器中运行的应用程序访问电脑硬盘里的文件.然而,随着基于浏览器(browser-based)的富因特网应用程序的增多,一些应用程序迫切需要访问用户所选择的文件,或者将文件保 ...
- SpringBoot Cache 深入
这上一篇文章中我们熟悉了SpringBoot Cache的基本使用,接下来我们看下它的执行流程 CacheAutoConfiguration 自动装配类 根据图中标注,看到它引用了CachingCon ...
- Python迭代器和生成器你学会了吗?
在了解什么是迭代器和生成器之前,我们先来了解一下容器的概念.对于一切皆对象来说,容器就是对象的集合.例如列表.元祖.字典等等都是容器.对于容器,你可以很直观地想象成多个元素在一起的单元:而不同容器的区 ...
- 【Python从入门到精通】(九)Python中字符串的各种骚操作你已经烂熟于心了么?
您好,我是码农飞哥,感谢您阅读本文,欢迎一键三连哦. 本文将重点介绍Python字符串的各种常用方法,字符串是实际开发中经常用到的,所有熟练的掌握它的各种用法显得尤为重要. 干货满满,建议收藏,欢迎大 ...
- Linux系统inodes资源耗尽时的查找及删除
for i in {1..10}; do echo $i; ls > $i.log; done for i in $(seq 1 10); do echo $i; done 以上为for循环的使 ...
- 华为高级研究员谢凌曦:下一代AI将走向何方?盘古大模型探路之旅
摘要:为了更深入理解千亿参数的盘古大模型,华为云社区采访到了华为云EI盘古团队高级研究员谢凌曦.谢博士以非常通俗的方式为我们娓娓道来了盘古大模型研发的"前世今生",以及它背后的艰难 ...
- 《Do Neural Dialog Systems Use the Conversation History Effectively? An Empirical Study》
https://zhuanlan.zhihu.com/p/73723782 请复制粘贴到markdown 查看器查看! Do Neural Dialog Systems Use the Convers ...
- Selenium3自动化测试【20】CSS定位元素
CSS 指层叠样式表 (CascadingStyleSheets),CSS一种用来表现HTML或XML等文件样式的计算机语言,其能够灵活的为页面提供丰富样式的风格. CSS使用选择器为页面元素绑定属性 ...
- Windows配置深度学习环境详细教程(一):安装Pycharm和Miniconda、conda环境介绍
序言 对于想要入门Python或者深度学习的初学者而言,配置环境一直是一个令人头疼的问题.相信有许多人经历过安装第三方包失败,安装好了却在使用中报错,安装CUDA.tensorflow.pytorch ...
- python框架之Flask
介绍:Flask是一个使用 Python 编写的轻量级 Web 应用框架.其 WSGI 工具箱采用 Werkzeug ,模板引擎则使用 Jinja2 . WSGl:Web Server Gateway ...