项目地址:

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

Jvm在启动一个程序的时候,会涉及到非常多的内容,其中就有一些JNI方法。拿 System.out 来说,这个变量是 final 修饰的,按照一般情况来说,我们是无法更改这个变量的值的:

public final static PrintStream out = null;

但是 System 类提供了一个 setOut 方法:

		public static void setOut(PrintStream out) {
        checkIO();
        setOut0(out);
    }

可以看出是使用了 native 方法来实现变量的赋值。

我们之前一直是使用 hack 的方式来打印输出:

if ("println".equals(methodRef.getName())) {
    print(methodRef, operandStack);
    operandStack.popRef();
    return;
}

如果想真正的实现 System.out.println 方法,我们就需要实现这些JNI方法。

本地方法实现

本地方法在class文件中没有Code属性,所以需要主动给maxStack和maxLocals字段赋值。

本地方法帧的操作数栈至少要能容纳返回值,为了简化代码,暂时给maxStack字段赋值为4。

因为本地方法帧的局部变量表只用来存放参数值,所以把argSlotCount赋给maxLocals字段刚好。

至于code字段,也就是本地方法的字节码,它本是没有字节的,但是我们需要模拟本地方法的调用,所以就自己设定一个操作符 0xFE,它表示 invokenative,返回指令则根据函数的返回值选择相应的返回指令。

我们在创建Method的时候,判断:

if (method.isNative()) {
    method.injectCodeAttribute(methodDescriptor.getReturnType());
}

injectCodeAttribute 里面就加入两条指令:

		private void injectCodeAttribute(String returnType) {
        this.maxStack = 4;
        this.maxLocals = argsSlotCount;
        switch (returnType.charAt(0)) {
            case 'V':
                this.code = new byte[]{(byte) 0xfe, (byte) 0xb1}; // return
                return;
            case 'D':
                this.code = new byte[]{(byte) 0xfe, (byte) 0xaf}; // dreturn
                return;
            case 'F':
                this.code = new byte[]{(byte) 0xfe, (byte) 0xae}; // freturn
                return;
            case 'J':
                this.code = new byte[]{(byte) 0xfe, (byte) 0xad}; // lreturn
                return;
            case 'L':
            case '[':
                this.code = new byte[]{(byte) 0xfe, (byte) 0xb0}; // areturn
                return;
            default:
                this.code = new byte[]{(byte) 0xfe, (byte) 0xac}; // ireturn
        }
    }

这样我们的 NativeMethod 就创建好了,但是还有一个问题,就是这个 code 里面没有逻辑,我们该如何实现该方法的具体逻辑呢?