本章中,分析了虚拟机在执行代码时,如何找到正确的方法、如何执行方法内的字节码,以及执行代码时涉及的内存结构。
执行引擎是Java虚拟机最核心的组成部分之一。两种执行方式:解释执行和编译执行。
运行时栈帧结构
是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈的栈元素。
组成部分:
- 局部变量表
- 操作数栈
- 动态连接
- 方法返回地址
- 一些额外的附加信息
当前栈帧:位于栈顶的栈帧
当前方法:与这个当前栈帧相关联的方法
概念结构图:
局部变量表
是一组变量存储空间,用于存放方法参数和方法内部定义的局部变量。
最小单位:变量槽(Slot)
- GC不会回收
- GC能回收
操作数栈
称为操作栈,它是一个后入先出栈。
元素:任意的Java数据类型。
容量:
- 32位数据类型占容量1
- 64位数据类型占容量2
动态连接
每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。符号引用分为两部分,一部分是静态解析,另一部分是动态连接。
静态解析:符号引用一部分在类加载阶段或者第一次使用的时候就转化为直接引用。
动态连接:符号引用另一部分将在每一次运行期间转化为直接引用。
方法返回地址
在方法退出之后,都需要返回到方法的指定位置,程序才能继续执行,方法返回可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态。
返回方式:
- 执行引擎遇到任意一个方法返回的字节码指令
- 在方法执行过程中遇到异常
附加信息
在实际开发中,一般会把动态连接、方法返回地址与其他附加信息全部归为一类,称为栈帧信息。
方法调用
方法调用并不等于方法执行,方法调用阶段唯一的任务就是确定被调用方法的版本(即调用哪一个方法)。所有方法调用中的目标方法在Class文件里面都是一个常量池中的符号引用。
解析
调用目标在程序代码写好、编译器进行编译时就必须确定下来。这类方法的调用称为解析。
方法分类:
- 静态方法:与类型直接关联
- 私有方法:在外部不可被访问
5条方法调用字节码指令:
示例:
分派
Java三大特征:继承、封闭和多态。分派调用过程将会提示多态性特征的一些最基本的体现。
静态分派:(重载)
静态类型(外观类型):Human
实际类型:man = new Man()…
加载原则:
- 虚拟机在重载时是通过参数的静态类型而不是实际类型作为判断依据的
- 重载方法匹配优先级(char->int->long->float->double)
动态分派(重写)
在运行期根据实际类型确定方法执行版本的分派过程称为动态分派。
invokervirtual指令的运行时解析过程:
单分派与多分派
方法的宗量:方法的接收者与方法的参数统称为方法的宗量。
分类:
- Java语言的静态分派属于多分派类型
- Java语言的动态分派属性单分派类型
虚拟机动态分派的实现
虚拟机基于性能考虑,大部分使用”稳定优化“手段——建立一个虚方法表。
虚方法表:
动态类型语言支持
关键特征是它的类型检查的主体过程是在运行期而不是在编译期。
- JDK1.7与动态类型
新增了invokedynamic指令. - java.lang.invoke包(类似C指针)
作用:提供一种新的目标方法机制——MethodHandler - invokedynamic指令
与MethodHandler作用一样. - 掌控方法分派规则
基于栈的字节码解释执行引擎
探讨虚拟机是如何执行方法中的字节码指令的。
解释执行
基于栈的指令集与基于寄存器的指令集
两者区别:基于栈的指令集可移植。
缺点:基于栈的指令集执行速度相对来说慢。