用Java手写jvm之模拟方法调用指令invokexxx和方法返回指令xreturn

发布于:2024-08-11 ⋅ 阅读:(159) ⋅ 点赞:(0)

写在前面

源码
本文一起看下方法调用相关的指令invokexxx以及方法返回(栈帧弹出线程栈)相关的指令xReturn

1:正文

因为invokexxx指令和普通的指令不同,会创建一个新的栈帧,并压倒操作数栈中,所以我们首先需要来定义一个公共的创建栈帧的方法来让ivvokestatic,invokeinterface等使用,如下:

public class MethodInvokeLogic {

    public static void invokeMethod(Frame invokerFrame, Method method) {
        if (method.name.equalsIgnoreCase("returnALong")) {
            System.out.println("------invoke xxxx 指令,做如下的事情;------");
            System.out.println("1:从当前栈帧中获取对应的执行线程");
            System.out.println("2:为要执行的方法创建对应的栈帧,并将栈帧压倒线程栈,作为当前栈帧");
            System.out.println("3:如果是有入参的话,则从调用栈帧的操作数栈中弹出入参,并设置到新栈帧的局部变量表中,这样被调用方法执行时就可以通过load指令从局部变量中获取操作");
        }
        Thread thread = invokerFrame.thread();
        Frame newFrame = thread.newFrame(method);
        thread.pushFrame(newFrame);
        // 将方法需要的参数设置到新栈帧的局部变量表中
        int argSlotCount = method.argSlotCount();
        if (argSlotCount > 0) {
            for (int i = argSlotCount - 1; i >= 0; i--) {
                Slot slot = invokerFrame.operandStack().popSlot();
                newFrame.localVars().setSlot(i, slot);
            }
        }

        //hack
        if (method.isNative()) {
            if ("registerNatives".equals(method.name())) {
                thread.popFrame();
            } else {
                throw new RuntimeException("native method " + method.name());
            }
        }
    }

}

这里我们以invokestatic指令和lreturn指令为例来看下对应的模拟代码,invokestatic指令:

public class INVOKE_STATIC extends InstructionIndex16 {

    @Override
    public void execute(Frame frame) {
        RunTimeConstantPool runTimeConstantPool = frame.method().clazz().constantPool();
        MethodRef methodRef = (MethodRef) runTimeConstantPool.getConstants(this.idx);
        Method resolvedMethod = methodRef.ResolvedMethod();

        if (!resolvedMethod.isStatic()) {
            throw new IncompatibleClassChangeError();
        }

        Class clazz = resolvedMethod.clazz();
        // 确保初始化完成(加载,链接,初始化中的初始化,即类加载的最后一步)
        if (!clazz.initStarted()) {
            frame.revertNextPC();
            ClassInitLogic.initClass(frame.thread(), clazz);
            return;
        }
        // 执行方法(即生成栈帧并压入到线程栈)
        MethodInvokeLogic.invokeMethod(frame, resolvedMethod);
    }
}

lreturn:

public class LRETURN extends InstructionNoOperands {

    @Override
    public void execute(Frame frame) {
        System.out.println("------lreturn 指令执行,做如下的事情:------");
        System.out.println("1:弹出当前的方法栈帧");
        System.out.println("2:获取上一个方法");
        System.out.println("3:从当前方法的操作数栈中获取执行结果,并推送到上一个方法的操作数栈中");
        Thread thread = frame.thread();
        Frame currentFrame = thread.popFrame();
        Frame invokerFrame = thread.topFrame();
        long val = currentFrame.operandStack().popLong();
        // 获取上一个方法,并将结果压倒其操作数栈的栈顶,这样,上一个就可以通过pop指令获取结果
        invokerFrame.operandStack().pushLong(val);
    }

}

main类:

/**
 * -Xthejrepath     D:\programs\javas\java1.8/jre -Xthetargetclazz     D:\test\itstack-demo-jvm-master\tryy-too-simulate-classload-load-clazz\target\test-classes\org\itstack\demo\test\HelloWorld
 */
public class Main {

    public static void main(String[] args) {
        Cmd cmd = Cmd.parse(args);
        if (!cmd.ok || cmd.helpFlag) {
            System.out.println("Usage: <main class> [-options] class [args...]");
            return;
        }
        if (cmd.versionFlag) {
            //注意案例测试都是基于1.8,另外jdk1.9以后使用模块化没有rt.jar
            System.out.println("java version \"1.8.0\"");
            return;
        }
        startJVM(cmd);
    }

    private static void startJVM(Cmd cmd) {
        // 创建classpath
        Classpath cp = new Classpath(cmd.thejrepath, cmd.classpath);
//        System.out.printf("classpath:%s class:%s args:%s\n", cp, cmd.getMainClass(), cmd.getAppArgs());
        System.out.printf("classpath:%s parsed class:%s \n", cp, cmd.thetargetclazz);
        //获取className
//        String className = cmd.getMainClass().replace(".", "/");
        try {
//            byte[] classData = cp.readClass(className);
            /*byte[] classData = cp.readClass(cmd.thetargetclazz.replace(".", "/"));
            System.out.println(Arrays.toString(classData));
            System.out.println("classData:");
            for (byte b : classData) {
                //16进制输出
                System.out.print(String.format("%02x", b & 0xff) + " ");
            }*/
            // 创建类加载器准备加载类
            /**
             * 加载3个阶段
             * 1:加载
             *      找到字节码,并将其存储到原元空间(<=7方法区),然后该类,该类父类,父接口也加载并在堆中生成对应的Class对象
             * 2:链接
             *      验证:验证文件内容的合法性,如是否cafebabe打头,结构是否符合定义
             *      准备:主要是给静态变量申请内存空间,以及赋初始值,如int,short这种则给默认值0
             *      解析:符号引用(指向类或者方法的一个字符串)转换为直接引用(jvm的内存地址)
             * 3:初始化
             *      执行<init>,<clinit>方法,完成静态变量的赋值
             */
            ClassLoader classLoader = new ClassLoader(cp);
            String clazzName = cmd.thetargetclazz.replace(".", "/");
            Class mainClass = classLoader.loadClass(clazzName);
            Method mainMethod = mainClass.getMainMethod();
            new Interpreter(mainMethod, true);


            /*// 创建className对应的ClassFile对象
            ClassFile classFile = loadClass(clazzName, cp);
            MemberInfo mainMethod = getMainMethod(classFile);
            if (null == mainMethod) {
                System.out.println("Main method not found in class " + cmd.classpath);
                return;
            }
            // 核心重点代码:通过解释器来执行main方法
            new Interpreter(mainMethod);*/
        } catch (Exception e) {
            System.out.println("Could not find or load main class " + cmd.getMainClass());
            e.printStackTrace();
        }
    }

    /**
     * 获取main函数,这里我们要模拟是执行器执行main函数的过程,当然其他方法也是一样的!!!
     * @param classFile
     * @return
     */
    private static MemberInfo getMainMethod(ClassFile classFile) {
        if (null == classFile) return null;
        MemberInfo[] methods = classFile.methods();
        for (MemberInfo m : methods) {
            if ("main".equals(m.name()) && "([Ljava/lang/String;)V".equals(m.descriptor())) {
                return m;
            }
        }
        return null;
    }

    /**
     * 生成class文件对象
     * @param clazzName
     * @param cp
     * @return
     */
    private static ClassFile loadClass(String clazzName, Classpath cp) {
        try {
            // 获取类class对应的byte数组
            byte[] classData = cp.readClass(clazzName);
            return new ClassFile(classData);
        } catch (Exception e) {
            System.out.println("无法加载到类: " + clazzName);
            return null;
        }
    }

}

定义需要加载的类:

public class HelloWorld {

    public static void main(String[] args) {
      /*  long x = fibonacci(10);
        System.out.println(x);*/
        returnALong();
    }

    public static long returnALong() {
        long longResult = 99;
        return longResult;
    }
    //斐波那契数列(Fibonacci sequence)
    /*private static long fibonacci(long n) {
        if (n <= 1) {
            return n;
        } else {
            return fibonacci(n - 1) + fibonacci(n - 2);
        }
    }*/

}

定义main的program argument:

-Xthejrepath
D:\programs\javas\java1.8/jre
-Xthetargetclazz
D:\test\itstack-demo-jvm-master\tryy-too-simulate-invokexxx-and-xreturn\target\test-classes\org\itstack\demo\test\HelloWorld

在这里插入图片描述
最后运行:
在这里插入图片描述

写在后面

参考文章列表

JVM 虚拟机字节码指令表

jvm方法调用指令invokestatic,invokespecial,invokeinterface,invokevirutal分析