深入理解Java虚拟机——虚拟机字节码执行引擎

深入理解Java虚拟机——虚拟机字节码执行引擎

概述

这里写图片描述

解释执行:通过解释器实时将字节码解释执行。

编译执行:通过JIT即时编译器产生本地代码执行。

Java虚拟机在执行class字节码的时候有解释执行和编译执行两种选择。

运行时栈帧结构

  • 虚拟机栈是线程私有的,存放着一个个栈帧。
  • 栈帧是方法执行时的数据结构,每一个方法的调用和返回都对应着栈帧的入栈和出栈
  • 一个栈帧包括:局部变量表、操作数栈、动态连接、返回地址。

栈帧中需要的局部变量表和操作数栈的大小在编译器就确定了。

局部变量表

局部变量表的容量以变量槽(Slot)为最小单位,一个Slot至少是32位。在32位机子上,Long和Double需要用2个Slot来存放。

下面我们通过测试代码来理解局部变量表的内容:

public class Test {

    int add(int a, int b) {
        int c = a + b;
        return c;
    }

    public static void main(String[] args) {
        System.out.println(new Test().add(1,2));
    }

}

编译:
javac Test.java

查看字节码:
javap -verbose Test.class

  int add(int, int);
    descriptor: (II)I
    flags:
    Code:
      stack=2, locals=4, args_size=3
         0: iload_1
         1: iload_2
         2: iadd
         3: istore_3
         4: iload_3
         5: ireturn
      LineNumberTable:
        line 8: 0
        line 9: 4
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       6     0  this   LTest;
            0       6     1     a   I
            0       6     2     b   I
            4       2     3     c   I

在上面的字节码中,Code字段定义了操作数栈(stack)的深度、局部变量表(locals)的深度和参数列表大小(args_size)

同时,LocalVariableTable字段代表的是局部变量表的内容,依次是:
1. 如果是实例方法,则第一个Slot存放指向实例的引用,也就相当于this关键字
2. 接着存放方法的参数列表
3. 最后存放的是方法里用到的局部变量

当方法执行超出了局部变量的作用域时,它占用的Slot可以被其他局部变量复用。

操作数栈

对于操作数栈而言,无论是32位机还是64位机,32位数据类型占的栈容量为1,64位数据类型占的栈容量为2。

两个栈帧之间可以因为虚拟机的优化而出现重叠:调用者的局部变量表区域与被调用者的操作数栈区域重叠,这样做的好处是避免了额外的参数传递。

动态连接

Class文件中有大量的符号引用,这些符号引用一部分在类加载阶段就转化为直接引用;另一部分则在运行期间动态地转化为直接引用。这称为动态连接。

实现动态连接需要栈帧中包含该栈帧所属方法的引用。

方法返回地址

使用调用者的PC计数器的值作为返回地址。

方法调用的版本选择

对于静态方法、私有方法,由于它们不会被重写,不需要进行多态选择,所以它们适合在类加载阶段进行解析。

invokestatic和invokespecial指令调用的方法,都可以在解析阶段确定唯一的调用版本,因此它们在类加载阶段就会把符号引用替换为直接饮用。

而被invokevirtual指令调用的方法,由于需要进行多态选择,需要在运行期才把方法的符号引用转化为直接引用。(被final修饰的方法例外)


Java是一个静态多分派、动态单分派的语言。

静态多分派

静态分派是指在编译阶段,编译器根据变量的静态类型参数列表来决定方法的重载版本。

动态单分派

动态分派是指在运行期,编译期单单根据变量的实际类型来决定方法的重写版本。

基于栈的字节码执行引擎

主流的指令集架构有基于栈的基于寄存器的指令集架构。

这两种架构的优劣在于:
1. 基于栈的指令集更紧凑,相同字节可容纳更多的指令。
2. 基于栈的运算会产生相当多的入栈和出栈操作,频繁的内存访问,导致执行速度相比基于寄存器的会慢一些。

猜你喜欢

转载自blog.csdn.net/mingC0758/article/details/81174073