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

打印 上一主题 下一主题

主题 649|帖子 649|积分 1957

写在前面

本文来实验模拟类加载器加载class的过程。
1:程序

首先来定义类加载器类:
  1. /**
  2. * 类加载器
  3. * 正常应该有bootstrap,ext,app三个类加载器,这里简单起见,只用一个来模拟了
  4. */
  5. public class ClassLoader {
  6.     // bootstrap,ext,app 组合的classpath
  7.     private Classpath classpath;
  8.     // 类名->对应的Class类
  9.     private Map<String, Class> classMap;
  10.     public ClassLoader(Classpath classpath) {
  11.         this.classpath = classpath;
  12.         this.classMap = new HashMap<>();
  13.     }
  14.     public Class loadClass(String className) {
  15.         Class clazz = classMap.get(className);
  16.         if (null != clazz) return clazz;
  17.         try {
  18.             return loadNonArrayClass(className);
  19.         } catch (Exception e) {
  20.             e.printStackTrace();
  21.         }
  22.         return null;
  23.     }
  24.         private Class loadNonArrayClass(String className) throws Exception {
  25.         byte[] data = this.classpath.readClass(className);
  26.         if (null == data) {
  27.             throw new ClassNotFoundException(className);
  28.         }
  29.         // 1:加载,找到字节码并生成Class对象
  30.         Class clazz = defineClass(data);
  31.         // 2:链接 验证:有效性 准备:静态变量空间申请  解析:符号引用转直接引用
  32.         link(clazz);
  33.         // 3:初始化 调用init clinit方法
  34.         init(clazz);
  35.         return clazz;
  36.     }
  37.     private void init(Class clazz) {
  38.         System.out.println("todo 初始化类:" + clazz.name);
  39.     }
  40.     private void link(Class clazz) {
  41.         verify(clazz);
  42.         prepare(clazz);
  43.     }
  44.     private void prepare(Class clazz) {
  45.         calcInstanceFieldSlotIds(clazz);
  46.         calcStaticFieldSlotIds(clazz);
  47.         allocAndInitStaticVars(clazz);
  48.     }
  49.     // ...
  50. }
复制代码
方法defineClass(data)对应了加载阶段,link(clazz)对应了链接阶段(包罗验证,准备,解析),init(clazz);对应了初始化。
为了更加全面的模拟这个过程,我们还自定义了Class类,和Method类,如下:
  1. public class Class {
  2.     // 类访问修饰符
  3.     public int accessFlags;
  4.     // 类名称
  5.     public String name;
  6.     // 父类名称,因为Java是单继承多实现所以这里的父类只有1个
  7.     public String superClassName;
  8.     // 父接口名称们,因为Java是单继承多实现,所以这里的父接口是多个
  9.     public String[] interfaceNames;
  10.     public RunTimeConstantPool runTimeConstantPool;
  11.     // 类的字段信息们
  12.     public Field[] fields;
  13.     // 类的方法信息们,包含有方法对应的指令码数组信息,本地变量表和操作数栈大小信息
  14.     public Method[] methods;
  15.     // 加载本类的类加载器
  16.     public ClassLoader loader;
  17.     // 父类对应的Class对象
  18.     public Class superClass;
  19.     public Class[] interfaces;
  20.     public int instanceSlotCount;
  21.     public int staticSlotCount;
  22.     public Slots staticVars;
  23.     // ...
  24. }
复制代码
  1. public class Method extends ClassMember {
  2.     // 操作数栈大小
  3.     public int maxStack;
  4.     // 本地变量表大小
  5.     public int maxLocals;
  6.     // 指令码字节数组
  7.     public byte[] code;
  8.     // ...
  9. }
  10. /**
  11. * 类成员对象
  12. * 字段
  13. * 方法
  14. * 因为字段和方法有些共享的内容,比如访问修饰符,名称,所属的class等,所以定义该公共父类
  15. */
  16. public class ClassMember {
  17.     // 字段、方法的访问修饰符
  18.     public int accessFlags;
  19.     // 方法,字段的名称
  20.     public String name;
  21.     // 方法,字段类型的描述
  22.     // 如private String name [Ljava/lang/String;
  23.     // 如public void (String name) {} ([Ljava/lang/String;)V
  24.     public String descriptor;
  25.     // 所属的类对应的Class对象
  26.     public Class clazz;
  27.     // ...
  28. }
复制代码
核心就是这些,具体的照旧需要投入时间来看源码,来多debug,接着写测试类:
  1. /**
  2. * -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
  3. */
  4. public class Main {
  5.     public static void main(String[] args) {
  6.         Cmd cmd = Cmd.parse(args);
  7.         if (!cmd.ok || cmd.helpFlag) {
  8.             System.out.println("Usage: <main class> [-options] class [args...]");
  9.             return;
  10.         }
  11.         if (cmd.versionFlag) {
  12.             //注意案例测试都是基于1.8,另外jdk1.9以后使用模块化没有rt.jar
  13.             System.out.println("java version "1.8.0"");
  14.             return;
  15.         }
  16.         startJVM(cmd);
  17.     }
  18.     private static void startJVM(Cmd cmd) {
  19.         // 创建classpath
  20.         Classpath cp = new Classpath(cmd.thejrepath, cmd.classpath);
  21. //        System.out.printf("classpath:%s class:%s args:%s\n", cp, cmd.getMainClass(), cmd.getAppArgs());
  22.         System.out.printf("classpath:%s parsed class:%s \n", cp, cmd.thetargetclazz);
  23.         //获取className
  24. //        String className = cmd.getMainClass().replace(".", "/");
  25.         try {
  26. //            byte[] classData = cp.readClass(className);
  27.             /*byte[] classData = cp.readClass(cmd.thetargetclazz.replace(".", "/"));
  28.             System.out.println(Arrays.toString(classData));
  29.             System.out.println("classData:");
  30.             for (byte b : classData) {
  31.                 //16进制输出
  32.                 System.out.print(String.format("%02x", b & 0xff) + " ");
  33.             }*/
  34.             // 创建类加载器准备加载类
  35.             /**
  36.              * 加载3个阶段
  37.              * 1:加载
  38.              *      找到字节码,并将其存储到原元空间(<=7方法区),然后该类,该类父类,父接口也加载并在堆中生成对应的Class对象
  39.              * 2:链接
  40.              *      验证:验证文件内容的合法性,如是否cafebabe打头,结构是否符合定义
  41.              *      准备:主要是给静态变量申请内存空间,以及赋初始值,如int,short这种则给默认值0
  42.              *      解析:符号引用(指向类或者方法的一个字符串)转换为直接引用(jvm的内存地址)
  43.              * 3:初始化
  44.              *      执行<init>,<clinit>方法,完成静态变量的赋值
  45.              */
  46.             ClassLoader classLoader = new ClassLoader(cp);
  47.             String clazzName = cmd.thetargetclazz.replace(".", "/");
  48.             Class mainClass = classLoader.loadClass(clazzName);
  49.             Method mainMethod = mainClass.getMainMethod();
  50.             new Interpreter(mainMethod);
  51.             /*// 创建className对应的ClassFile对象
  52.             ClassFile classFile = loadClass(clazzName, cp);
  53.             MemberInfo mainMethod = getMainMethod(classFile);
  54.             if (null == mainMethod) {
  55.                 System.out.println("Main method not found in class " + cmd.classpath);
  56.                 return;
  57.             }
  58.             // 核心重点代码:通过解释器来执行main方法
  59.             new Interpreter(mainMethod);*/
  60.         } catch (Exception e) {
  61.             System.out.println("Could not find or load main class " + cmd.getMainClass());
  62.             e.printStackTrace();
  63.         }
  64.     }
  65.     /**
  66.      * 获取main函数,这里我们要模拟是执行器执行main函数的过程,当然其他方法也是一样的!!!
  67.      * @param classFile
  68.      * @return
  69.      */
  70.     private static MemberInfo getMainMethod(ClassFile classFile) {
  71.         if (null == classFile) return null;
  72.         MemberInfo[] methods = classFile.methods();
  73.         for (MemberInfo m : methods) {
  74.             if ("main".equals(m.name()) && "([Ljava/lang/String;)V".equals(m.descriptor())) {
  75.                 return m;
  76.             }
  77.         }
  78.         return null;
  79.     }
  80.     /**
  81.      * 生成class文件对象
  82.      * @param clazzName
  83.      * @param cp
  84.      * @return
  85.      */
  86.     private static ClassFile loadClass(String clazzName, Classpath cp) {
  87.         try {
  88.             // 获取类class对应的byte数组
  89.             byte[] classData = cp.readClass(clazzName);
  90.             return new ClassFile(classData);
  91.         } catch (Exception e) {
  92.             System.out.println("无法加载到类: " + clazzName);
  93.             return null;
  94.         }
  95.     }
  96. }
复制代码
定义需要加载的类,加载之后会解析该类main函数的指令码并使用自定义的解释器来实行代码:
  1. package org.itstack.demo.test;
  2. /**
  3. * -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
  4. */
  5. public class HelloWorld {
  6.    
  7.     public static void main(String[] args) {
  8.         int sum = 0;
  9.         for (int i = 1; i <= 100; i++) {
  10.             sum += i;
  11.         }
  12.         System.out.println(sum);
  13.     }
  14. }
复制代码
配置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之模拟解释器实行指令码 。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

泉缘泉

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表