执行引擎是java虚拟机核心的组成部分之一
运行时栈帧的结构
jvm以方法作为最基本的执行单元 栈帧则是用于支持虚拟机进行方法调用和方法执行背后的数据结构 也是虚拟机运行时数据区中的虚拟机栈的栈元素
栈帧存储了方法的局部变量表 操作数栈 动态链接 和方法返回地址 附加信息 每一个方法从调用开始到执行结束的过程都对应着一个栈帧在虚拟机栈里从入栈到出栈的过程
局部变量表
是一组变量值的存储空间 用于存放方法参数和方法内部定义的局部变量
在java程序被编译成Class文件时 在方法的Code属性中确定了该方法所需分配的局部变量表的最大容量
容量以变量槽为最小单位 一个变量槽可以存放一个32位以内的数据结构
long和double为64位 可以分割为两次32位读写
操作数栈
也称操作栈 一个后入先出的栈 同局部变量表一样 其最大深度也在编译的时候在方法的Code属性中确定了最大深度 操作数栈中每一个元素都可以是任意数据类型 32位数据类型所占栈容量是1 64位数据类型所占栈容量是2
一个方法刚开始执行时 操作数栈为空 执行过程中会有各种字节码指令往操作数栈中写入和提取内容(出栈入栈操作)
执行引擎是基于栈的执行引擎 栈即指操作数栈
动态链接
每个栈帧都包含一个指向运行时常量池中栈帧所属方法的引用 这个引用是为了支持方法调用过程中的动态链接
方法返回地址
一个方法开始执行后 只有两种方式退出这个方法
- 执行引擎遇到任意一个方法返回的字节码指令 称为“正常调用完成”
- 在方法执行过程中遇到异常 并且该异常没有在方法体内得到处理 称为“异常调用完成”
无论哪种退出方式 都必须返回到最初方法被调用的位置 程序才能继续执行可能需要栈帧中保存一些信息 用来恢复它的上层返回地址
附加信息
例如一些与调试 性能收集相关的信息 取决于虚拟机的实现
方法调用
方法调用不等同于方法中代码被执行 只是确定被调用哪个方法
在程序运行中 是最频繁的操作之一
解析
在类加载的解析阶段 将一部分符号引用转化为直接引用
调用目标在代码写好 编译器进行编译的那一刻就确定了
分派
- 静态分派(重载)
public class HumanOrMan {
static abstract class Human{
}
static class Man extends Human{
}
public void sayHello(Man man) {
System.out.println("hello man");
}
public void sayHello(Human man) {
System.out.println("hello Human");
}
public static void main(String[] args) {
Human man = new Man();
HumanOrMan a = new HumanOrMan();
a.sayHello(man);
}
}
输入结果为hello Human
编译器在编译程序时不知道一个对象的实际类型是什么 但是能知道静态类型是Human 虚拟机根据参数的静态类型决定了使用哪个重载版本 所有依赖静态类型来决定方法执行版本的分派动作都成为 静态分派
- 动态分派(重写)
public class HumanOrMan {
static abstract class Human{
protected abstract void sayHello();
}
static class Man extends Human{
@Override
protected void sayHello() {
System.out.println("hello man");
}
}
static class Woman extends Human{
@Override
protected void sayHello() {
System.out.println("hello woman");
}
}
public static void main(String[] args) {
Human man = new Man();
Human woman = new Woman();
woman.sayHello();
man.sayHello();
man = new Woman();
man.sayHello();
}
}
结果为
hello woman
hello man
hello woman
这里选择调用的方法版本是根据实际类型