0%

JVM(8)-虚拟机字节码执行引擎

本文是《深入理解Java虚拟机(第二版)》第八章的笔记

运行时栈帧结构

在活动线程中,只有栈顶的栈时有效的,称为当前栈帧,与这个栈帧相关联的方法称为当前方法。下面对栈帧的4个主要部分进行分析。

局部变量表

存放方法参数和方法内部定义的局部变量

一些细节:

  • 最小存储单元(Slot),一个Slot可以存32位以内的数据类型。
  • boolean、byte、char、short、int、float、reference、returnAddress占一个Slot;long、double占两个Slot,是非原子的,但它是线程安全的,因为它是栈中的,是线程私有的。
  • 对于实例方法,第一个Slot是传递所属对象实例的引用,也就是我们常用的 this
  • 注意局部变量没有初始值哦。
  • 当一个变量的pc寄存器的值大于Slot的作用域时,Slot是可以复用的。

操作数栈

  • 虚拟机字节码执行引擎是“基于栈的执行引擎”,这个栈就是操作数栈。
  • 对比基于寄存器的执行引擎

优点:可移植性、代码更加紧凑、编译器实现更紧凑。确定就是速度更慢。

  • 下面栈帧的部分操作数栈与上面的栈帧的部分局部变量是重叠的。

动态连接

指向运行时常量池中该栈帧所属方法的引用,这个引用的为了支持方法调用过程的动态连接。具体内容在下面的方法调用中解释。

方法返回地址

方法退出(也就是当前栈帧出栈)的两种方式:

  • return正常退出
  • 异常退出,根据异常表得返回出口

方法调用

方法调用不等同于方法的执行,方法调用阶段唯一的任务就是确定被调用方法的版本。说白了就是找方法,方法唯一就直接确定(解析)。方法不唯一:重载(静态分配)、重写(动态分配)

解析调用

  • 调用目标在程序代码写好、编译器进行编译时就确定好的。这类方法时调用称为解析。是静态的。

  • 非虚方法:静态方法、私有方法、实例构造器、父类方法、final方法。它们都是采用解析调用。反之其它就是虚方法。

分派调用

Human man = new Man();

Human是静态类型(外观类型),Man是实际类型

静态分派

依赖静态类型来定位方法执行的版本的分配动作称为静态分配。最典型的应用是方法重载。

  • 编译器在重载时是根据参数的静态类型作为依据的。
  • 由于字面量没有显式的静态类型,它重载时可能会有多种选择,只是选一个更好的版本。
  • 静态分配和解析不是互斥的,例如静态方法也是可以重载的。
  • 注意:静态分配更严格,一定是要用静态类型做参数

动态分派

依赖实际类型来定位方法执行的版本的分配动作称为动态分配。最典型的应用是方法重写。

  • 虚拟器在重写时是对象的实际类型作为依据的。
  • 注意:动态分配更宽松,如果实际类型中没有对应的方法,就会向上找父类里的相同方法来调用。

一个测试例子:

public class MixTest {
static class Human{ }
static class Man extends Human{}
static class Woman extends Human{}

public static class Father {
public void choice(Human arg) {
System.out.println("father choose human");
}

public void choice(Man arg) {
System.out.println("father choose man");
}

public void choice(Woman arg) {
System.out.println("father choose woman"); // 和同一类里的同名方法是重载关系
}
}

public static class Son extends Father {
public void choice(Human arg) {
System.out.println("son choose human");
}

public void choice(Man arg) {
System.out.println("son choose man"); // 和父类的同名同参数方法是重写关系
}

public void choice(Woman arg) {
System.out.println("son choose woman");
}
}

public static void main(String[] args) {
Human woman = new Woman();
Man man = new Man();
Father father = new Father();
Father son = new Son();
father.choice(woman); // 重写:对象类型选Father(实际类型) 重载:参数类型选 Human(静态类型)
son.choice(man); // 重写:对象类型选Son(实际类型) 重载:参数类型选 Man(静态类型)
}
}

/* 程序输出:
father choose human
son choose man
*/