用Java手写jvm之模拟类加载器加载class

news2024/9/26 1:20:17

写在前面

本文来尝试模拟类加载器加载class的过程。

1:程序

首先来定义类加载器类:

/**
 * 类加载器
 * 正常应该有bootstrap,ext,app三个类加载器,这里简单起见,只用一个来模拟了
 */
public class ClassLoader {
    // bootstrap,ext,app 组合的classpath
    private Classpath classpath;
    // 类名->对应的Class类
    private Map<String, Class> classMap;

    public ClassLoader(Classpath classpath) {
        this.classpath = classpath;
        this.classMap = new HashMap<>();
    }

    public Class loadClass(String className) {
        Class clazz = classMap.get(className);
        if (null != clazz) return clazz;
        try {
            return loadNonArrayClass(className);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

        private Class loadNonArrayClass(String className) throws Exception {
        byte[] data = this.classpath.readClass(className);
        if (null == data) {
            throw new ClassNotFoundException(className);
        }
        // 1:加载,找到字节码并生成Class对象
        Class clazz = defineClass(data);
        // 2:链接 验证:有效性 准备:静态变量空间申请  解析:符号引用转直接引用
        link(clazz);
        // 3:初始化 调用init clinit方法
        init(clazz);
        return clazz;
    }

    private void init(Class clazz) {
        System.out.println("todo 初始化类:" + clazz.name);
    }

    private void link(Class clazz) {
        verify(clazz);
        prepare(clazz);
    }

    private void prepare(Class clazz) {
        calcInstanceFieldSlotIds(clazz);
        calcStaticFieldSlotIds(clazz);
        allocAndInitStaticVars(clazz);
    }
    // ...
}

方法defineClass(data)对应了加载阶段,link(clazz)对应了链接阶段(包含验证,准备,解析),init(clazz);对应了初始化。

为了更加全面的模拟这个过程,我们还自定义了Class类,和Method类,如下:

public class Class {
    // 类访问修饰符
    public int accessFlags;
    // 类名称
    public String name;
    // 父类名称,因为Java是单继承多实现所以这里的父类只有1个
    public String superClassName;
    // 父接口名称们,因为Java是单继承多实现,所以这里的父接口是多个
    public String[] interfaceNames;
    public RunTimeConstantPool runTimeConstantPool;
    // 类的字段信息们
    public Field[] fields;
    // 类的方法信息们,包含有方法对应的指令码数组信息,本地变量表和操作数栈大小信息
    public Method[] methods;
    // 加载本类的类加载器
    public ClassLoader loader;
    // 父类对应的Class对象
    public Class superClass;
    public Class[] interfaces;

    public int instanceSlotCount;
    public int staticSlotCount;
    public Slots staticVars;

    // ...
}
public class Method extends ClassMember {
    // 操作数栈大小
    public int maxStack;
    // 本地变量表大小
    public int maxLocals;
    // 指令码字节数组
    public byte[] code;
    // ...
}

/**
 * 类成员对象
 * 字段
 * 方法
 * 因为字段和方法有些共享的内容,比如访问修饰符,名称,所属的class等,所以定义该公共父类
 */
public class ClassMember {
    // 字段、方法的访问修饰符
    public int accessFlags;
    // 方法,字段的名称
    public String name;
    // 方法,字段类型的描述
    // 如private String name [Ljava/lang/String;
    // 如public void (String name) {} ([Ljava/lang/String;)V
    public String descriptor;
    // 所属的类对应的Class对象
    public Class clazz;

    // ...
}

核心就是这些,具体的还是需要投入时间来看源码,来多debug,接着写测试类:

/**
 * -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);


            /*// 创建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;
        }
    }

}

定义需要加载的类,加载之后会解析该类main函数的指令码并使用自定义的解释器来执行代码:

package org.itstack.demo.test;

/**
 * -Xjre D:\programs\javas\java1.8/jre D:\test\itstack-demo-jvm-master\try-too-simulate-interpreter\target\test-classes\org\itstack\demo\test\HelloWorld
 */
public class HelloWorld {
    
    public static void main(String[] args) {
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            sum += i;
        }
        System.out.println(sum);
    }
}

配置program argument-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:
在这里插入图片描述
运行:
在这里插入图片描述

写在后面

参考文章列表

第23讲 | 请介绍类加载过程,什么是双亲委派模型? 。

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

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

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

相关文章

入门 PyQt6 看过来(案例)20~ 动态树

​ 1 QTreeWidget树类 QTreeWidget类可以呈现数组、数列等数据&#xff0c;并且可以进行交互&#xff0c;它使用标准的数据模型&#xff0c;其单元格数据通过QTableWidgetItem对象来实现。 QTreeWidget继承自QTreeView&#xff0c;是封装了默认Model的QTreeView&#xff0c;其…

C++ | Leetcode C++题解之第312题戳气球

题目&#xff1a; 题解&#xff1a; class Solution { public:int maxCoins(vector<int>& nums) {int n nums.size();vector<vector<int>> rec(n 2, vector<int>(n 2));vector<int> val(n 2);val[0] val[n 1] 1;for (int i 1; i &l…

ElasticSearch入门(六)SpringBoot2

private String author; Field(name “word_count”, type FieldType.Integer) private Integer wordCount; /** Jackson日期时间序列化问题&#xff1a; Cannot deserialize value of type java.time.LocalDateTime from String “2020-06-04 15:07:54”: Failed to des…

Django文件上传

【图书介绍】《Django 5企业级Web应用开发实战&#xff08;视频教学版&#xff09;》_django 5企业级web应用开发实战(视频教学版)-CSDN博客 《Django 5企业级Web应用开发实战&#xff08;视频教学版&#xff09;》(王金柱)【摘要 书评 试读】- 京东图书 (jd.com) 本节主要介…

【计算机网络】IP地址和子网掩码(子网掩码篇)

个人主页:【😊个人主页】 系列专栏:【❤️计算机网络】 文章目录 前言什么是子网掩码?子网掩码的组成组成规则表示方法子网掩码的分类标准(默认)子网掩码:变长子网掩码(VLSM):全零和全一子网掩码:子网掩码的计算确定IP地址类别及默认子网掩码计算子网掩码根据子网数…

橙单中台化低代码生成器

​橙单中台化低代码生成器 在当今快速发展的软件开发领域&#xff0c;橙单中台化低代码生成器凭借其强大的功能和灵活的架构&#xff0c;成为了开发者不可或缺的利器。本文将介绍橙单的基本信息、特点以及如何快速部署和使用。 软件简介 橙单中台化低代码生成器是一款开源的低…

被华为的AI扩图震惊到,超自然超好看!

美颜、P图对大家来说都不陌生&#xff0c;但是近期在互联网实火的各种AI扩图你了解多少&#xff1f;它既能满足图片构图时进行延伸美化&#xff0c;又能在未知的创意里无限探索。 近期&#xff0c;华为Pura 70系列手机获推HarmonyOS 4.2.0.172 更新&#xff0c;华为Pura 70 Pr…

事务性邮件API的功能优势?考虑哪些指标?

事务性邮件API的性能如何优化&#xff1f;怎么选择邮件API接口&#xff1f; 在当今数字化时代&#xff0c;企业需要一种高效、可靠的方法与客户沟通。事务性邮件API成为解决这一需求的重要工具。AokSend将探讨事务性邮件API的功能优势及考虑的关键指标。 事务性邮件API&#…

如何在 Kubernetes 中使用 ClickHouse 和 JuiceFS

ClickHouse 结合 JuiceFS 一直是一个热门的组合&#xff0c;社区中有多篇实践案例。今天的文章来自美国公司 Altinity&#xff0c;一家提供 ClickHouse 商业服务的企业&#xff0c;作者是 Vitaliy Zakaznikov&#xff0c;他尝试了这个组合并公开了过程中使用的代码。原文有两篇…

基于DreamBooth的“妙鸭相机”——一次不太成功的实践

重磅推荐专栏: 《大模型AIGC》 《课程大纲》 《知识星球》 本专栏致力于探索和讨论当今最前沿的技术趋势和应用领域,包括但不限于ChatGPT和Stable Diffusion等。我们将深入研究大型模型的开发和应用,以及与之相关的人工智能生成内容(AIGC)技术。通过深入的技术解析和实践经…

Encoder-Decoder:Seq2seq

目录 一、编码器解码器架构&#xff1a;1.定义&#xff1a;2.在CNN中的体现&#xff1a;3.在RNN中的体现&#xff1a;4.代码&#xff1a; 二、Seq2seq&#xff1a;1.模型架构&#xff1a;1.1编码器&#xff1a;1.2解码器&#xff1a; 2.架构细节&#xff1a;3.模型评估指标BLEU…

C# Unity 补全计划 泛型

本文仅作学习笔记与交流&#xff0c;不作任何商业用途&#xff0c;作者能力有限&#xff0c;如有不足还请斧正 1.什么是泛型 泛型&#xff08;Generics&#xff09;是C#中的一个强大特性&#xff0c;允许你编写可以适用于多种数据类型的可重用代码&#xff0c;而不需要重复编写…

第二证券:刚刚!亚太股市,跌麻了!

今天早盘&#xff0c;亚太股市全线崩跌。日经225指数在大幅低开之后快速下行&#xff0c;最大跌幅近5%&#xff1b;韩国、澳大利亚股指亦迎来逾越2%以上的暴降。那么&#xff0c;毕竟发生了什么&#xff1f; 剖析人士认为&#xff0c;或许仍是与日元套息有关。从前史来看&…

Duplicate class kotlin.collections.jdk8.CollectionsJDK8Kt found in modules。Android studio纯java代码报错

我使用java代码 构建项目&#xff0c;初始代码运行就会报错。我使用的是Android Studio Giraffe&#xff08;Adroid-studio-2022.3.1.18-windows&#xff09;。我在网上找的解决办法是删除重复的类&#xff0c;但这操作起来真的太麻烦了。 这是全部报错代码&#xff1a; Dupli…

mysql环境的部署安装及数据库的操作(twenty day)

一、centos7 中安装 mysql 8.x 1、下载安装包 wget https://downloads.mysql.com/archives/get/p/23/file/mysql-8.0.33-1.el7.x86_64.rpm-bundle.tar 2、解压 tar -zxvf mysql-8.0.33-1.el7.x86_64.rpm-bundle.tar 3、卸载mariodb yum remove -y *mariadb* 4、依次安装依赖包…

SC215TA是C型/ PD和DPDM快速充电控制器,集成了内部反馈补偿PD3.0快充

SC215TA是C型/ PD和DPDM快速充电控制器&#xff0c;集成了内部反馈补偿。它符合最新的C型和PD 3.0标准&#xff0c;并支持专有的高压快速充电协议与DPDM接口。它的目标是旅行适配器的应用程序。SC215TA通过集成USB PD基带PHY、Type-C检测、DPDM PHY、VBUS放电路径、VCONN电源、…

旧衣回收小程序,旧衣回收行业新态势

进入网络时代后&#xff0c;互联网改变了大众的生活&#xff0c;传统的回收模式逐渐被淘汰&#xff0c;新兴的互联网旧衣回收受到了大众的关注&#xff01;通过技术创新为行业带来新模式&#xff0c;不断优化回收流程&#xff0c;提高回收效率&#xff0c;提升居民的回收体验&a…

Java编程达人:每日一练,提升自我

目录 题目1.以下哪个单词不是 Java 的关键字&#xff1f;2.boolean 类型的默认值为&#xff1f;3.以下代码输出正确的是&#xff1f;4.以下代码&#xff0c;输出结果为&#xff1a;5.以下代码输出结果为&#xff1a;6.以下代码输出结果为&#xff1f;7.float 变量的默认值为&am…

Three.js WebGPU 节点材质系统 控制instances的某个实例单独的透明度,颜色等属性

文章目录 1. 声明一个实例必要的属性instanceMatrix同级别的属性2. 在设置位置矩阵的时候填充这个数组3. 在shader中获取当前的索引4. 增加uniform5. 对比当前着色的实例是否是选中的实例6. 如果是选中的实例7. 影响片元着色器透明度参数 8.源码 写在前面 本文环境是 原生js 没…

EV代码签名证书申请流程

EV代码签名证书可以有效提高用户信赖。可以用于任何软件&#xff0c;支持Microsoft SmartScreen应用程序信誉功能以及对Windows 10内核驱动程序进行签名。 下面是EV代码签名证书的申请流程 代码签名证书_代码签名证书申请购买-JoySSL代码签名证书是对可执行脚本、软件代码和内容…