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!");
}
}
字节码中存放编码后的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。
可以看到,该指令的操作码是0xB2,助记符是getstatic。
它的操作数是0x0002,代表常量池里的第二个常量。
在第4章中讨论过,操作数栈和局部变量表只存放数据的值,并不记录数据类型。
结果就是:指令必须知道自己在操作什么类型的数据。这一点也直接反映在了操作码的助记符上。
例如,
iadd
指令就是对int
值进行加法操作;
dstore
指令把操作数栈顶的double
值弹出,存储到局部变量表中;
areturn
从方法中返回引用
值。
也就是说,如果某类指令可以操作不同类型的变量,则助记符的第一个字母表示变量类型。
Java虚拟机规范把已经定义的205条指令按用途分成了11类:
- 常量(constants)指令:
把常量推入操作数栈顶。常量可以来自三个地方:隐含在操作码里、操作数和运行时常量池。常量指令共有21条
加载(loads)指令、
存储(stores)指令、
操作数栈(stack)指令、
数学(math)指令、
转换(conversions)指令、
比较(comparisons)指令、
控制(control)指令、
引用(references)指令、
扩展(extended)指令
保留(reserved)指令。
保留指令一共有3条。其中一条是留给调试器的,用于实现断点,操作码是202(0xCA),助记符是
breakpoint
。
另外两条留给Java虚拟机实现内部使用,操作码分别是254(0xFE)和266(0xFF),助记符是impdep1
和impdep2
。
这三条指令不允许出现在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()
}