08 字节码执行引擎

Wu Jun 2020-01-09 20:44:18
05 Java > 01 Java 虚拟机

1 运行时栈帧结构

image

1.1 局部变量表

1.2 操作数栈

1.3 动态连接

1.4 方法返回地址

1.5 附加信息

2 方法调用

2.1 解析

解析调用是一个静态的过程,在编译期间就完全确定,在类加载的解析阶段就会把涉及的符号引用全部转变为可确定的直接引用;而分派调用则可能是静态的也可能是动态的;

2.2 分派

1)静态分派

语句中 Human 称为变量的静态类型,后面的 Man 称为变量的实际类型

Human man = new Man();

所有根据静态类型来定位方法执行版本的分派动作称为静态分派,其典型应用是方法重载

2)动态分派

invokevirtual 指令执行的第一步就是在运行期间确定接收者的实际类型,所以两次调用中 invokevirtual 指令把常量池中的类方法符号引用解析到了不同的直接引用上,这个过程就是 Java 语言中方法重写的本质;

在运行期根据实际类型确定方法执行版本的分派过程称为动态分派;

3)单分派与多分派

方法的接收者与方法的参数统称为方法的宗量,根据分派基于多少种宗量,可以将分派分为

今天的 Java 语言是一门静态多分派、动态单分派的语言;

4)虚拟机动态分派的实现

在方法区中建立一个虚方法表(Virtual Method Table),使用虚方法表索引来代替元数据查找以提高性能;

方法表一般在类加载的连接阶段进行初始化,准备了类的变量初始化值后,虚拟机会把该类的方法表也初始化完毕;

2.3 动态类型语言支持

3 基于栈的字节码解释执行引擎

3.1 解释执行

3.2 基于栈的指令集与基于寄存器的指令集

3.3 基于栈的解释器执行过程

一段简单的算法代码

public int calc(){
    int a = 100;
    int b = 200;
    int c = 300;
    return (a + b) * c;
}

上述代码的字节码表示

public int calc();
    Code:
        Stack=2, Locals=4, Args_size=1
        0:bipush 100
        2:istore_1
        3:sipush 200
        6:istore_2
        7:sipush 300
        10:istore_3
        11:iload_1
        12:iload_2
        13:iadd
        14:iload_3
        15:imul
        16:ireturn
}

javap 提示这段代码需要深度为 2 的操作数栈和 4 个 Slot 的局部变量空间,作者根据这些信息画了示意图来说明执行过程中的变化情况:

执行偏移地址为 0 的指令 image

执行偏移地址为 2 的指令 image

执行偏移地址为 11 的指令 image

执行偏移地址为 12 的指令 image

执行偏移地址为 13 的指令 image

执行偏移地址为 14 的指令 image

执行偏移地址为 16 的指令 image

注:上面的执行过程仅仅是一种概念模型,虚拟机中解析器和即时编译器会对输入的字节码进行优化。