用Java手写jvm之模拟解释器执行指令码

news2024/11/16 7:29:46

写在前面

本文看下如何模拟解释器执行指令码。需要一些前置内容:
用Java手写jvm之系列 中的前4篇文章,当然如果你已经了解了这部分内容,也可以不看。

1:正文

既然是模拟解释器,我们肯定要先来定义一个解释器类了:

/**
 * 解释器,负责基于指令码来执行具体程序
 */
public class Interpreter {

    /**
     * 解释器构造函数,直接就开始干活了!!!
     *
     * @param m
     */
    public Interpreter(MemberInfo m) {
        // 获取代码信息
        CodeAttribute codeAttr = m.codeAttribute();
        // 获取局部变量表大小
        int maxLocals = codeAttr.maxLocals();
        // 获取操作数栈大小
        int maxStack = codeAttr.maxStack();
        // 获取字节码数组,也就是我们的代码在class文件中的形式
        byte[] byteCode = codeAttr.data();
        // 模拟启动一个要执行代码的线程
        Thread thread = new Thread();
        // 创建一个执行方法对应的栈帧
        Frame frame = thread.newFrame(maxLocals, maxStack);
        // 当栈帧推到线程栈顶,成为当前栈帧
        thread.pushFrame(frame);
        // 开始执行代码了
        loop(thread, byteCode);
    }


    private void loop(Thread thread, byte[] byteCode) {
        Frame frame = thread.popFrame();
        // 字节码读取器,负责维护程序计数器,以及读取指令码
        BytecodeReader reader = new BytecodeReader();

        while (true) {
            // 获取当前的程序计数器位置,并读取该位置的指令码
            int pc = frame.nextPC();
            // 设置程序计数器位置
            thread.setPC(pc);
            // 充值字节码读取器的字节码信息和位置信息
            reader.reset(byteCode, pc);
            // 获取指令码
            byte opcode = reader.readByte();
            // 根据指令码获取对应的执行指令的指令类
            Instruction inst = Factory.newInstruction(opcode);
            if (null == inst) {
                System.out.println("寄存器(指令)尚未实现 " + byteToHexString(new byte[]{opcode}));
                break;
            }
            // 获取操作数,比如IADD指令就需要从操作数的栈顶获取2个操作数,但并不是所有的指令都需要该操作
            // 比如iconst_0就不需要
            inst.fetchOperands(reader);
            // 设置程序计数器到方法栈帧中
            frame.setNextPC(reader.pc());
            System.out.println("寄存器(指令):" + byteToHexString(new byte[]{opcode}) + " -> " + inst.getClass().getSimpleName() + " => 局部变量表:" + JSON.toJSONString(frame.localVars().getSlots()) + " 操作数栈:" + JSON.toJSONString(frame.operandStack().getSlots()));
            // 真正的执行指令!!!
            inst.execute(frame);
        }

    }
    // ...
}

BytecodeReader类负责从指令码数组中读取指令码,原码如下:

package com.dahuyou.tryy.too.simulate.interpreter.instructions.base;

public class BytecodeReader {
    // 指令码字节数组
    private byte[] codes;
    // program counter,程序计数器
    private int pc;

    public void reset(byte[] codes, int pc) {
        this.codes = codes;
        this.pc = pc;
    }

    public int pc() {
        return this.pc;
    }

    // [go]int8 = [java]byte
    // 读1个字节,程序计数器+1
    public byte readByte() {
        byte code = this.codes[this.pc];
        this.pc++;
        return code;
    }

    //[go]int16 = [java]short
    // 读2个字节
    public short readShort() {
        byte byte1 = readByte();
        byte byte2 = readByte();
        return (short) ((byte1 << 8) | byte2);
    }

    // 读4个字节
    public int readInt() {
        int byte1 = this.readByte();
        int byte2 = this.readByte();
        int byte3 = this.readByte();
        int byte4 = this.readByte();
        return (byte1 << 24) | (byte2 << 16) | (byte3 << 8) | byte4;
    }

    // 读n个4个字节
    public int[] readInts(int n) {
        int[] ints = new int[n];
        for (int i = 0; i < n; i++) {
            ints[i] = this.readInt();
        }
        return ints;
    }

    //used by lookupswitch and tableswitcch
    // 跳过padding
    public void skipPadding() {
        while (this.pc % 4 != 0) {
            this.readByte();
        }
    }

}

代码byte opcode = reader.readByte();是获取要执行的指令码,程序Instruction inst = Factory.newInstruction(opcode);是根据指令码获取执行该指令码的指令对象,Instruction是一个接口,定义了执行一个指令需要的操作,如下:

package com.dahuyou.tryy.too.simulate.interpreter.instructions.base;

import com.dahuyou.tryy.too.simulate.interpreter.runtime.area.Frame;

public interface Instruction {

    /**
     * 获取操作数 比如IADD 就需要从栈顶获取2个整数,当然并不是所有的指令都需要这个操作,比如iconst_0,只需要将整数0压倒栈顶,并不需要从栈顶获取任何数据以供execute方法使用
     * @param reader
     */
    void fetchOperands(BytecodeReader reader);

    /**
     * 执行操作,如IADD,就是执行具体的相加操作了
     * @param frame
     */
    void execute(Frame frame);

    static void branch(Frame frame, int offset) {
        int pc = frame.thread().pc();
        int nextPC = pc + offset;
        frame.setNextPC(nextPC);
    }

}

inst.execute(frame);就是真正的执行指令了,比如IADD,就是执行两个整数的相加操作了,如下:

public class IADD extends InstructionNoOperands {

    @Override
    public void execute(Frame frame) {
        OperandStack stack = frame.operandStack();
        int v2 = stack.popInt();
        int v1 = stack.popInt();
        int res = v1 + v2;
        stack.pushInt(res);
    }

}

最后,因为指令时非常多的,当前有2百多个,所以,Instruction的子类也是非常多的,比如数学运算指令IADD,本地变量表,操作数栈的操作指令Store,load,大小比较指令if_icmpge,方法执行指令invokespecial,invokevirtual,对象创建指令new等。具体看原码吧!

然后来测试下效果,main类:

package com.dahuyou.tryy.too.simulate.interpreter;

import com.dahuyou.tryy.too.simulate.interpreter.parse.clazz.classpath.Classpath;
import com.dahuyou.tryy.too.simulate.interpreter.parse.clazz.clazzfile.ClassFile;
import com.dahuyou.tryy.too.simulate.interpreter.parse.clazz.clazzfile.MemberInfo;
import com.dahuyou.tryy.too.simulate.interpreter.parse.clazz.cmd.Cmd;

/**
 * -Xthejrepath D:\programs\javas\java1.8/jre -Xthetargetclazz D:\test\itstack-demo-jvm-master\try-too-simulate-interpreter\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) + " ");
            }*/
            String clazzName = cmd.thetargetclazz.replace(".", "/");
            // 创建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) {
        int sum = 0;
        for (int i = 1; i <= 10; i++) {
            sum += i;
        }
        System.out.println(sum);
    }
    
}

运行主函数前配置下program argument:

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

debug Interpreter类可以看下指令码都是啥内容:
在这里插入图片描述
也可以通过执行javap 看下是否与实际相符:
在这里插入图片描述
最后运行看效果:

classpath:com.dahuyou.tryy.too.simulate.interpreter.parse.clazz.classpath.Classpath@45283ce2 parsed class:D:\test\itstack-demo-jvm-master\try-too-simulate-interpreter\target\test-classes\org\itstack\demo\test\HelloWorld 
寄存器(指令):0x03 -> ICONST_0 => 局部变量表:[{"num":0},{"num":0},{"num":0}] 操作数栈:[{"num":0},{"num":0}]
寄存器(指令):0x3c -> ISTORE_1 => 局部变量表:[{"num":0},{"num":0},{"num":0}] 操作数栈:[{"num":0},{"num":0}]
寄存器(指令):0x04 -> ICONST_1 => 局部变量表:[{"num":0},{"num":0},{"num":0}] 操作数栈:[{"num":0},{"num":0}]
寄存器(指令):0x3d -> ISTORE_2 => 局部变量表:[{"num":0},{"num":0},{"num":0}] 操作数栈:[{"num":1},{"num":0}]
寄存器(指令):0x1c -> ILOAD_2 => 局部变量表:[{"num":0},{"num":0},{"num":1}] 操作数栈:[{"num":1},{"num":0}]
寄存器(指令):0x10 -> BIPUSH => 局部变量表:[{"num":0},{"num":0},{"num":1}] 操作数栈:[{"num":1},{"num":0}]
寄存器(指令):0xa3 -> IF_ICMPGT => 局部变量表:[{"num":0},{"num":0},{"num":1}] 操作数栈:[{"num":1},{"num":10}]
寄存器(指令):0x1b -> ILOAD_1 => 局部变量表:[{"num":0},{"num":0},{"num":1}] 操作数栈:[{"num":1},{"num":10}]
寄存器(指令):0x1c -> ILOAD_2 => 局部变量表:[{"num":0},{"num":0},{"num":1}] 操作数栈:[{"num":0},{"num":10}]
寄存器(指令):0x60 -> IADD => 局部变量表:[{"num":0},{"num":0},{"num":1}] 操作数栈:[{"num":0},{"num":1}]
寄存器(指令):0x3c -> ISTORE_1 => 局部变量表:[{"num":0},{"num":0},{"num":1}] 操作数栈:[{"num":1},{"num":1}]
寄存器(指令):0x84 -> IINC => 局部变量表:[{"num":0},{"num":1},{"num":1}] 操作数栈:[{"num":1},{"num":1}]
寄存器(指令):0xa7 -> GOTO => 局部变量表:[{"num":0},{"num":1},{"num":2}] 操作数栈:[{"num":1},{"num":1}]
寄存器(指令):0x1c -> ILOAD_2 => 局部变量表:[{"num":0},{"num":1},{"num":2}] 操作数栈:[{"num":1},{"num":1}]
寄存器(指令):0x10 -> BIPUSH => 局部变量表:[{"num":0},{"num":1},{"num":2}] 操作数栈:[{"num":2},{"num":1}]
寄存器(指令):0xa3 -> IF_ICMPGT => 局部变量表:[{"num":0},{"num":1},{"num":2}] 操作数栈:[{"num":2},{"num":10}]
寄存器(指令):0x1b -> ILOAD_1 => 局部变量表:[{"num":0},{"num":1},{"num":2}] 操作数栈:[{"num":2},{"num":10}]
寄存器(指令):0x1c -> ILOAD_2 => 局部变量表:[{"num":0},{"num":1},{"num":2}] 操作数栈:[{"num":1},{"num":10}]
寄存器(指令):0x60 -> IADD => 局部变量表:[{"num":0},{"num":1},{"num":2}] 操作数栈:[{"num":1},{"num":2}]
...

写在后面

参考文章列表

用Java手写jvm之系列 。

jvm之字节码 。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1966974.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一个灵活、可扩展的开源问答平台,可用于社区论坛、帮助中心、知识管理等多种场景

大家好&#xff0c;今天给大家分享的是一个开源的问答平台软件Apache Incubator-Answer&#xff0c;由 SegmentFault 思否团队于 2022 年 10 月 24 日正式开源&#xff0c;并于同年入选 Apache 软件基金会孵化器。 项目介绍 Apache Incubator-Answer旨在为任何规模的团队提供一…

【C语言版】数据结构教程(一)绪论(上)

【内容简介】本文整理数据结构&#xff08;C语言版&#xff09;相关内容的复习笔记&#xff0c;供各位朋友借鉴学习。本章内容更偏于记忆和理解&#xff0c;请读者们耐心阅读。 数据结构教程 绪论&#xff08;上&#xff09; 本节学习目标 1.1 基本概念 1.2 抽象数据类型的表示…

苹果电脑怎么录制屏幕?3招教你轻松录制,高效实用

随着数字化时代的快速发展&#xff0c;屏幕录制已经成为我们日常工作和生活中不可或缺的一部分。它不仅是展示产品、教授知识、分享经验的重要工具&#xff0c;更是我们展现个性和创造力的新舞台。在苹果电脑上&#xff0c;屏幕录制功能的应用更是将这一体验推向了新的高度。 …

优思学院|不良产品留到客户产线上了,8D报告要如何写?

8D问题解决法是一个经常用作公司内部改善以及应付客户投诉的关键方法&#xff0c;不过&#xff0c;在改善的过程中却有一些误区&#xff0c;如果没有注意&#xff0c;那么这份8D报告将会变得徒劳无功。这里有一个这样的案例&#xff1a; 一个经验丰富的工程师把客户图纸看错了&…

【面向PM考试】挣值分析的计算场景介绍

前言 PM考试目前还是挺火热的&#xff0c;有些人是因为行业所需&#xff0c;有些人是因为自身学习&#xff0c;总而言之&#xff0c;需要经过系统的学习&#xff0c;才能胜任当下的工作。 关于挣值分析&#xff0c;包括一些基础概念&#xff0c;博主有一篇文章已详细介绍&…

基于ip/域名/端口的server配置、nodej项目、部署nfs服务器

回顾复习 jdk环境 tomcat服务器需要jdk环境 版本对应 tomcat>jdk17 tomcat9>jdk1.8 tomvat10>jdk17 1、配置系统变量 JAVA_HOME sed -i $aexport JAVA_HOME/usr/local/jdk22/ /etc/profile sed -i $aexport PATH$JAVA_HOME/bin:$PATH /etc/profile sour…

猫用空气净化器应该如何挑选?国内养猫空气净化器哪个好?

有没有友友跟我一样是鼻炎患者&#xff0c;可偏偏家里两只猫都是掉毛怪&#xff0c;行走的大型蒲公英&#xff0c;多猫家庭确实很快乐&#xff0c;但一到换毛季&#xff0c;家里地上、空气里全是猫毛。每天都需要拼命的吸地板&#xff08;累鼠个人&#xff09;&#xff0c;毛一…

金牌九宫格!经常跑步的人,没有一个是弱者——早读(逆天打工人爬取热门微信文章解读)

我在学习龙头战法&#xff0c;有了解的吗&#xff1f; 引言Python 代码第一篇 洞见 经常跑步的人&#xff0c;没有一个是弱者第二篇 今天尝试结尾 引言 时间是什么&#xff1f; 我越来越觉得是一个限定 因为没有时间 我们很多事情就有点乱套 你说你的 我说我的 十分混乱 没有一…

Matplotlib面积图绘制秘籍:让你的数据‘膨胀’起来,但不吹泡泡哦!

1. 引言 嘿&#xff0c;数据迷们&#xff01;想不想让你的数据‘活’起来&#xff0c;跳一曲色彩斑斓的面积舞&#xff1f;Matplotlib面积图&#xff0c;不只是数字的堆砌&#xff0c;它是故事的讲述者&#xff0c;让复杂数据变得一目了然&#xff0c;还带点小幽默。快来一探究…

第1天:Python基础语法(五)

正文&#xff1a; 在之前的文章中&#xff0c;我们已经学习了Python的基本语法集合和集合的一些常用操作。 在本篇文章中&#xff0c;我们将继续学习其他类型 字符串格式化 使用操作符%s来实现 ➢ 几个%s就几个变量 ➢ 超过一个变量时&#xff0c;需要用元组%&#xff08;…

SpringBoot SseEmitter,服务器单项消息推送

防止推送消息乱码 import org.jetbrains.annotations.NotNull; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.servlet.mvc.method…

阿里云实时计算Flink在多行业的应用和实践

摘要&#xff1a;本文整理自 Flink Forward Asia 2023 中闭门会的分享。主要分享实时计算在各行业的应用实践&#xff0c;对回归实时计算的重点场景进行介绍以及企业如何使用实时计算技术&#xff0c;并且提供一些在技术架构上的参考建议。内容分为以下四个部分&#xff1a; 业…

Magic-PDF:端到端PDF文档解析神器 构建高质量RAG必备!

项目结构 流程解析 预处理的作用是判断文档内容是否需要进行OCR识别&#xff0c;如果是普通可编辑的PDF文档&#xff0c;则使用PyMuPDF库提取元信息。 模型层除了常规的OCR、版面结构分析外&#xff0c;还有公式检测模型&#xff0c;可提取公式内容&#xff0c;用于后续把公式…

Ubuntu系统在两个屏幕上都显示任务栏

Ubuntu系统在两个屏幕上都显示任务栏 目标 希望在两个屏幕&#xff08;主屏和扩展屏&#xff09;上都显示下图的状态栏 解决方法 打开设置&#xff0c;找到>外观>Dock 2. 将显示于改成所有显示

明清进士人数数据

明清进士人数数据 指标&#xff1a;省份名称、城市名称、区县名称、明清各省进士人数、明清各城市进士人数、明清各县区进士人数 指标说明&#xff1a; Province[省份名称]-统计数据所属省份 City[城市名称]-统计数据所属地级市 Region[区县名称]-统计数据所属区县 MQpro…

ZooKeeper日志自动清理实用脚本

ZooKeeper日志自动清理:保持系统整洁的实用脚本 在管理ZooKeeper集群时,定期清理日志文件是一项重要但常被忽视的任务。本文将介绍一个简单而有效的bash脚本,用于自动清理ZooKeeper的日志和快照文件,并讨论如何使用cron来定期执行此脚本。 磁盘告警&#xff0c;所以写了一个脚…

如何用代码在数据库新建一个表格/HTML的跨行合并和跨列合并

1.用navicat新建一表格 数据库使用链接 2.前端代码 &#xff08;1&#xff09;跨行合并&#xff1a;rowspan“合并单元格的个数” 跨列合并&#xff1a;colspan“合并单元格的个数” <body> <table border"1”align"center” width"100%cellpaddin…

微信公众号,配置自定义菜单,跟回调授权网址配置入口,图讲解

微信公众号&#xff0c;配置自定义菜单&#xff0c;跟回调授权网址配置入口&#xff0c;图讲解

【图像识别】十大数据集合集!

本文将为您介绍10个经典、热门的数据集&#xff0c;希望对您在选择适合的数据集时有所帮助。 1 DanishFungi2020 发布方&#xff1a; Google 发布时间&#xff1a; 2021 简介&#xff1a; 补充材料&#xff1a;丹麦真菌 2020 - 不仅仅是另一个图像识别数据集为了支持细粒度植…

django网络爬虫系统- 计算机毕业设计源码81040

摘要 本论文主要论述了如何开发一个网络爬虫系统&#xff0c;对旅游景点信息进行爬取&#xff0c;本系统将严格按照软件开发流程进行各个阶段的工作&#xff0c;面向对象编程思想进行项目开发。在引言中&#xff0c;作者将论述网络爬虫系统的当前背景以及系统开发的目的&#x…