原始博客的地址:

https://lyldalek.notion.site/JVM-0x4-8a7220e379794989b3cc3b52c04c7c50

该项目的地址:

https://github.com/aprz512/write-your-own-jvm

上一篇文章,我们主要是实现了虚拟机栈,知道了方法执行的一个大致的流程。在这一篇文章中我们就要深入到方法的指令,实现指令的解析,以及实现一个简单的解释器。

解释器也是也是编译器的一种,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 个字节,并转换为有符号或者无符号数。之所以要区分有符号与无符号是因为不同的指令的操作数不同,有的操作数需要看作无符号数。