JVM 字节码执行引擎

本文将介绍 JVM 中的字节码执行引擎。

一、什么是字节码执行引擎?

字节码执行引擎是 JVM 的核心组成部分之一,其任务是 读取字节码并执行,是 “一次编写,处处运行” 这一特性的支撑。

二、运行时栈帧结构

具体请看:

JVM 内存区域 - 栈帧

三、方法调用

1. 什么是方法调用?

方法调用,其实更应该叫做 “方法确定”,即 确定要被调用的具体方法,而不是执行方法中的代码。

2. 解析调用

解析调用指的是对于 “编译期可知,运行期不可变” 的方法的确定。这类方法包括:静态方法、私有方法、final 方法、构造方法、父类方法,它们的具体方法在编译期就可以确认下来,并且不可能通过继承或别的方式重写出其它版本。

3. 重载解析调用

重载解析,又被称作 “静态分派”,两种说法均可。

重载解析调用指的是对于重载方法的确定。Java 提供了重载机制,允许定义若干个名字相同但参数不同的方法,待调用时再根据传入参数的数量及类型,自动选择对应的方法执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
abstract class Animal {

}

abstract class Dog extend Animal {

}

abstract class Cat extend Animal {

}

void sayHello(Animal animal) {
System.out.println("动物")
}

void sayHello(Dog dog) {
System.out.println("狗")
}

void sayHello(Cat cat) {
System.out.println("猫")
}

Animal dog = new Dog();
Animal cat = new Cat();
Dog dog1 = new Dog();
// 动物
sayHello(dog);
// 动物
sayHello(cat);
// 狗
sayHello(dog1);

重载方法的确认是根据参数的数量和 “定义类型” 进行判断的,而这两者都可以在编译时确认,因此重载方法也可以在编译时确认。

4. 分派

分派调用指的是对于重写方法的确定。Java 提供了重写机制,允许子类重写父类的方法。

重写方法的确定需要根据调用者的 “具体类型” 进行判断,而 “具体类型” 只有运行时才可以确认,因此只能在执行时根据 “具体类型” 来分派方法执行版本。

确认步骤大致为:

  • 确认调用对象的 “具体类型”,记作 C
  • 在 C 中寻找对应方法
  • 若未找到,按照继承关系逐级寻找

确认步骤具体落实到虚拟机上时,基于性能的考虑,虚拟机往往不会采用逐级向上搜索的方式。一种常见的做法是为类型在方法区中建立一个虚方法表,表中存放着类型的各个方法的实际引用。虚方法表往往在类加载的连接阶段初始化完成。通过虚方法表,对方法的确定不再需要逐级搜索,只需要查表即可。

四、字节码执行

通过方法调用确定的要调用的方法后,接下来就应该执行其中的字节码。

字节码的执行由 基于栈的字节码解释执行引擎 完成。

其中,

  • 基于栈:指令集架构有基于栈的指令集架构、基于寄存器的指令集架构,Java 编译器输出的字节码是基于栈的

  • 解释执行:Java 语言常被定义为解释执行,但是 JVM 演变到如今,许多虚拟机都包含了即时编译器,代码既可能被解释执行,也可能被编译执行

五、动态类型语言支持

1. 动态类型语言

动态语言的关键特征是:类型检查在运行期进行,而非在编译期。

典型的静态类型语言是 Java,典型的动态类型语言是 JavaScript。

2. 具体

略。

参考

  • 深入理解 Java 虚拟机