写在前面
本文来实验模拟类加载器加载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之模拟解释器实行指令码 。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |