深入理解 Java 虚拟机读书笔记7

「执行引擎」是虚拟机中核心部分,用于执行字节码。重点在于结构体系和指令集的制定。

栈帧

用于支持虚拟机进行方法调用和执行的数据结构,它是虚拟机栈的栈元素。一个方法对应一个栈帧,在虚拟机栈中的入栈出栈操作就对应着方法开始执行至结束的过程。

一个栈帧主要包括「局部变量表」「操作数栈」「动态连接」「方法返回地址」「额外附加信息」,在编译期,局部变量表和操作数栈的大小就已经确定了,并存入了 Class 文件的方法表 Code 属性中。关于 Class 文件结构可见 深入理解 Java 虚拟机读书笔记5

局部变量表

用于存放方法参数和局部变量。局部变量表的容量以「变量槽」为最小单位,但「变量槽」并没有规定具体大小。

在方法执行时,虚拟机通过局部变量表以索引定位的形式(从位置 0 的变量槽开始)完成参数值到参数变量列表的传递过程。如果方法是对象实例方法,那么局部变量表的第 0 位就是对象实例的引用。在分配完参数后,才分配方法内的变量。为了节省空间,变量槽可以重用。

需要注意的是,局部变量不像类变量,它没有「准备」阶段,所以不会有默认赋值,这也是为什么定义局部变量时,必须要给初始值的原因。

操作数栈

栈中的元素是任意的 Java 类型,执行操作就是入栈出栈的过程。Java 虚拟机的执行引擎称为“基于栈的执行引擎”,这个栈就是指操作数栈。

我觉得操作数栈的执行指令过程有点像编译原理当中的编译过程。

动态连接

Class 文件中的符号引用需要转化为直接引用,而转化方式分为 静态解析 和 动态连接。静态解析发生在类加载阶段(即「解析」阶段)或第一次使用的时候,动态连接发生在运行期间。

而在栈帧中包含了一个指向运行时常量池中该栈帧所属方法的引用,为的就是支持动态连接。

「解析」阶段即是方法调用,但注意对这些方法是有要求的,这些方法在程序真正运行前就有一个版本,且在运行期间不可变,我们称之为“编译期可知,运行期不可变”。具体这些方法包括,静态方法私有方法实例构造器父类方法final 方法,统称为“非虚方法”。

另外方法调用还有「分派调用」。分派时有可能是静态也有可能是动态,因此会有静态单分派,静态多分派,动态单分派,动态多分派。Java 语言是静态多分派,动态单分派。

  • 静态分派。典型应用就是方法重载,方法重载是通过参数的静态类型作为判断依据来决定调用哪个重载方法的。(TypeA a = new TypeB() 其中左边的 TypeA 就叫做静态类型,右边的 TypeB 就叫做实际类型)而静态类型是在编译期就可知的,所以,依赖静态类型来定位方法调用的就叫静态分派。

  • 动态分派。在运行期根据实际类型确定方法调用的过程。大致就是在操作数栈顶找到第一个元素所指向的实际类型,然后去常量池中匹配与实际类型中相同的方法(这步就可以理解为动态连接),如果满足就转化为直接引用了,如果不满足就依次再找父类的,直到找到或找不到。这个过程也就是方法重写的本质。

方法返回地址

执行方法最终会有两种退出方式:正常完成出口,异常完成出口。正常完成出口会给调用该方法的调用者返回结果,不管结果是什么类型或者什么值或者调用者关不关心。异常完成出口就不会。

附加信息

类似于备注,通常「动态连接」「方法返回地址」「附加信息」作为一个部分,称为「栈帧信息」。

你可能感兴趣的:(深入理解 Java 虚拟机读书笔记7)