• 自定义ClassLoader


    简单地纯粹地记录下如何进行自定义一个自己的ClassLoader

    什么双亲委派模型啊,双亲委派模型的破坏啊,好处啊,缺点啊什么的,一概不说。

    自定义ClassLoader的博客啥的,看过不少,但是就是没自己亲手写一下,今天尝试写一下,发现古人诚不欺我!

    纸上得来终觉浅,绝知此事要躬行

    失败版本

    最开始是这么写的

    public class MyClassLoader extends ClassLoader {
    
        @Override
        protected Class findClass (String name) throws ClassNotFoundException {
    
            String classPath = name.replace(".", "/");
            InputStream classInputStream = getSystemClassLoader().getResourceAsStream(classPath);
            try {
                byte[] classBytes = new byte[classInputStream.available()];
                classInputStream.read(classBytes);
                Class clazz = defineClass(name, classBytes, 0, classBytes.length);
                resolveClass(clazz);
                return clazz;
            } catch (IOException e) {
                throw new ClassNotFoundException();
            }
    
        }
    }
    

    这里错误比较多,不过还是记住了一个,我是重写了findClass方法,而不是重写了loadClass方法,推荐也是通过重写findClass方法,以前是重写loadClass方法的方式。

    即使是错误的,但是写之前还是绞尽脑汁的想了好久,试图把记忆中那点破碎的,分崩离析而又即将消失的关于自定义ClassLoader的记忆,给重新恢复了。可惜的是,我并不具体这个能力,凭着那点仅存的记忆,写下我的第一个自定义ClassLoader,很遗憾它是错误的。

    写完后,就去测试跑了下,发现并没有出现我期许的结果 。

    这里说下期许的结果是什么

    1. 加载class文件后生成的Class对象,调用其getClassLoader方法,应该是输出MyClassLoader
    2. Class对象和使用系统类加载器加载的同一个class代表的Class对象,并不相等,==会返回false
    3. 自定义类加载器加载的对象,是没办法强转成系统类加载器加载的Class类型。

    然后,没有一个结果符合预期的。

    看到输出的ClassLoader还是AppClassLoader,很奇怪,我明明自定义了类加载还去加载了啊!

    最终发现,直接继承ClassLoader时,使用默认的无参构造

    protected ClassLoader() {
        this(checkCreateClassLoader(), getSystemClassLoader());
    }
    

    默认情况下,继承自ClassLoader的子类,会拥有一个父类加载,就是 AppClassLoader,而 要加载的类 ,发现已经被父类加载器加载过了,所以实际上并没有子类的findClass方法

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        // 同步,保证加载的安全性
        synchronized (getClassLoadingLock(name)) {
            // 检查是否已经被加载了
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    // 存在父加载器,先委托给父加载器,双亲委派模型的体现 
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                      	// 不存在父加载器,使用启动类加载器去加载 
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                }
    	    // 如果以上都找不到,就使用下面的逻辑去查找 
                if (c == null) {
                    long t1 = System.nanoTime();
                    // 这个就是各个子类来实现的了
                    c = findClass(name);
    
                    // 一些信息记录,记录到虚拟机里
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
          	// 上面仅仅完成了一个加载class的动作,但是整个类的加载并没有完成
            // 如果需要解析,则会对Class对象进行解析,这个名字有误导性,其实这是类加载阶段的链接阶段 
            // 也就是 验证 准备 解析三个阶段
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
    

    所以问题就很明了了,

    第一次修改后的版本

    public class MyClassLoader extends ClassLoader {
        public MyClassLoader () {
          // 不使用系统类加载器作为此类加载的父加载器
          // 这样它的父加载器就是启动类加载器
          super(null);
        }
        @Override
        protected Class findClass (String name) throws ClassNotFoundException {
    
            String classPath = name.replace(".", "/");
            InputStream classInputStream = getSystemClassLoader().getResourceAsStream(classPath);
            try {
                byte[] classBytes = new byte[classInputStream.available()];
                classInputStream.read(classBytes);
                Class clazz = defineClass(name, classBytes, 0, classBytes.length);
                resolveClass(clazz);
                return clazz;
            } catch (IOException e) {
                throw new ClassNotFoundException();
            }
    
        }
    }
    

    这个一跑,也是完蛋,不过好解决。

    一般调用loadClass方法时,传的都是包名,这里是要去加载字节码的,也就是找class文件,所以要转换成具体的路径,这里的路径使用的是相对路径,类位于classpath目录下,所以直接使用ClassLoader#getResourceAsStream就可以获取class文件的字节流`了。

    这里实现的字节码来源是从文件系统加载的class文件,实际上任何符合Java虚拟机规范的Class结构的字节数组,都可以被加载进来,动态代理就是在运行时生成字节码,然后直接加载的。

    可运行版本

    public class MyClassLoader extends ClassLoader {
    
        public MyClassLoader () {
            super(null);
    
        }
    
        @Override
        protected Class findClass (String name) throws ClassNotFoundException {
    
            String classPath = name.replace(".", "/")+".class";
            InputStream classInputStream = getSystemClassLoader().getResourceAsStream(classPath);
            try {
                byte[] classBytes = new byte[classInputStream.available()];
                classInputStream.read(classBytes);
                Class clazz = defineClass(name, classBytes, 0, classBytes.length);
                resolveClass(clazz);
                return clazz;
            } catch (IOException e) {
                throw new ClassNotFoundException();
            }
    
        }
    }
    

    这就是一个麻雀虽小五脏俱全的自定义类加载器了。

    两个重要知识点

    就想到这俩,肯定不止俩

    同一个类的Class对象在同一个虚拟机进程中,可以存在多个实例,在虚拟机中,是根据Class所属的类加载器,来确定唯一一个Class

    Hotspot虚拟机在进行类加载时,采用了类似的TLAB的方式,会给每个类加载器分配一块内存,这样这个类加载器加载的类,直接在这里分配,提高效率,也便于管理,不过遇到有很多类加载的话,会出现OOM的可能,原因就是每个类加载器分配一块,多整一些 ,空间不够了,OOM

    TLAB(Thread Local Allocate Buffer),目的是提升性能的,每一个线程在新生代的Eden区都有一个自己的一亩三分地,这样在分配内存时,不需要加锁做同步,提升分配的效率。

  • 相关阅读:
    java 算法最长连续递增子序列
    java 算法最长连续递增序列
    最大连续子数组和(Java)
    mysql时间序列与窗口函数
    CSS控制br高度
    小知识随手记(九):兄弟选择器(~和+)区别
    VUE组件递归实现自定义目录及拖拽效果
    VUE的插件解析
    VUE的mixin混入解析
    VUE高阶组件解析
  • 原文地址:https://www.cnblogs.com/heartlake/p/12980009.html
Copyright © 2020-2023  润新知