原始博客的地址:
https://lyldalek.notion.site/JVM-0x4-8a7220e379794989b3cc3b52c04c7c50
该项目的地址:
上一篇文章,我们主要是实现了虚拟机栈,知道了方法执行的一个大致的流程。在这一篇文章中我们就要深入到方法的指令,实现指令的解析,以及实现一个简单的解释器。
解释器也是也是编译器的一种,java文件编译成class文件是前端编译器完成的,java程序在运行的时候对字节码的执行使用的是解释器,也叫后端编译器。
通常将指令分为两部分:
每条指令都以一个单字节的操作码(opcode)开头,这意味着Java虚拟机最多只能支持256条指令。
Java虚拟机规范给每个操作码都指定了一个助记符(mnemonic)。
比如操作码是0x00这条指令,因为它什么也不做,所以它的助记符是nop(no operation)。
JVM的指令中,操作数不是固定的,可能有0个,1个,2个等等。
前面我们解析了 class 文件,拿到了每个 method 的 code 属性,使用的是 byte[] 储存的:
public class CodeAttribute extends AttributeInfo {
private final byte[] code;
}
要解析指令,我们需要遵循每条指令的格式。我们定义一个 CodeReader
类,它负责读取固定字节的数据并进行转换:
public class CodeReader {
private byte[] code;
private int pc;
public void reset(byte[] code, int pc) {
this.code = code;
this.pc = pc;
}
public int readByte() {
byte b = code[pc];
pc += 1;
return b;
}
/**
* remove extended sign bit
*/
public int readUnsignedByte() {
byte b = code[pc];
pc += 1;
return b & 0xFF;
}
public int readShort() {
int b1 = readUnsignedByte();
int b2 = readUnsignedByte();
return (short) (b1 << 8 | b2);
}
/**
* remove extended sign bit
*/
public int readUnsignedShort() {
int b1 = readUnsignedByte();
int b2 = readUnsignedByte();
return (b1 << 8 | b2) & 0xFFFF;
}
public int readInt() {
int b1 = readUnsignedByte();
int b2 = readUnsignedByte();
int b3 = readUnsignedByte();
int b4 = readUnsignedByte();
return b1 << 24 | b2 << 16 | b3 << 8 | b4;
}
public int[] readInts(int count) {
int[] result = new int[count];
for (int i = 0; i < count; i++) {
result[i] = readInt();
}
return result;
}
public void skipPadding() {
while (pc % 4 != 0) {
readByte();
}
}
public int getPc() {
return pc;
}
}
该类主要是读取 1/2/4 个字节,并转换为有符号或者无符号数。之所以要区分有符号与无符号是因为不同的指令的操作数不同,有的操作数需要看作无符号数。