第5章 指令集和解释器

Java虚拟机顾名思义,就是一台虚拟的机器,而字节码(bytecode)就是运行在这台虚拟机器上的机器码
每一个类或者接口都会被Java编译器编译成一个class文件,类或接口的方法信息就放在class文件的method_info结构中 。
如果方法不是抽象的,也不是本地方法,方法的Java代码就会被编译器编译成字节码(即使方法是空的,编译器也会生成一条return语句),存放在method_info结构的Code属性中。

package jvmgo.book.ch03;

public class ClassFileTest {
    
    public static final boolean FLAG = true;
    public static final byte BYTE = 123;
    public static final char X = 'X';
    public static final short SHORT = 12345;
    public static final int INT = 123456789;
    public static final long LONG = 12345678901L;
    public static final float PI = 3.14f;
    public static final double E = 2.71828;
    
    public static void main(String[] args) throws RuntimeException {
        System.out.println("Hello, World!");
    }
}

第5章 指令集和解释器_第1张图片
方法信息就放在class文件的method_info结构中,方法的Java代码编译成字节码存method_info的Code属性的code子属性中

字节码中存放编码后的Java虚拟机指令。每条指令都以一个单字节的操作码(opcode)开头,这就是字节码名称的由来。由于只使用一字节表示操作码,显而易见,Java虚拟机最多只能支持256(2^8 )条指令。
到第八版为止,Java虚拟机规范已经定义了205条指令,操作码分别是0(0x00)到202(0xCA)、254(0xFE)和255(0xFF)。
这205条指令构成了Java虚拟机的指令集(instruction set)。

和汇编语言类似,为了便于记忆,Java虚拟机规范给每个操作码都指定了一个助记符(mnemonic)。比如操作码是0x00这条指令,因为它什么也不做,所以它的助记符是nop(no operation)。

Java虚拟机使用的是变长指令,操作码后面可以跟零字节或多字节的操作数(operand)。如果把指令想象成函数的话,操作数就是它的参数。为了让编码后的字节码更加紧凑,很多操作码本身就隐含了操作数,比如把常数0推入操作数栈的指令是iconst_0。

第5章 指令集和解释器_第2张图片
0xB2

可以看到,该指令的操作码是0xB2,助记符是getstatic。

它的操作数是0x0002,代表常量池里的第二个常量。


第5章 指令集和解释器_第3张图片
0x0002

在第4章中讨论过,操作数栈和局部变量表只存放数据的值,并不记录数据类型。
结果就是:指令必须知道自己在操作什么类型的数据。这一点也直接反映在了操作码的助记符上。
例如,
iadd指令就是对int值进行加法操作;
dstore指令把操作数栈顶的double值弹出,存储到局部变量表中;
areturn从方法中返回引用值。
也就是说,如果某类指令可以操作不同类型的变量,则助记符的第一个字母表示变量类型。

第5章 指令集和解释器_第4张图片

Java虚拟机规范把已经定义的205条指令按用途分成了11类:

  • 常量(constants)指令:
    把常量推入操作数栈顶。常量可以来自三个地方:隐含在操作码里、操作数运行时常量池。常量指令共有21条

加载(loads)指令、
存储(stores)指令、
操作数栈(stack)指令、
数学(math)指令、
转换(conversions)指令、
比较(comparisons)指令、
控制(control)指令、
引用(references)指令、
扩展(extended)指令
保留(reserved)指令。

保留指令一共有3条。其中一条是留给调试器的,用于实现断点,操作码是202(0xCA),助记符是breakpoint
另外两条留给Java虚拟机实现内部使用,操作码分别是254(0xFE)和266(0xFF),助记符是impdep1impdep2
这三条指令不允许出现在class文件中。

5.2 指令和指令解码

Java虚拟机规范的2.11节介绍了Java虚拟机解释器的大致逻

do {
  atomically calculate pc and fetch opcode at pc;
  if (operands) fetch operands;
  execute the action for the opcode;
} while (there is more to do);

每次循环都包含三个部分:
计算pc、指令解码、指令执行。
go伪代码

for {
  pc := calculatePC()
  opcode := bytecode[pc]
  inst := createInst(opcode)
  inst.fetchOperands(bytecode)
  inst.execute()
}

你可能感兴趣的:(第5章 指令集和解释器)