美文网首页
类加载器 ClassLoader

类加载器 ClassLoader

作者: a_salt_fish | 来源:发表于2019-03-02 21:48 被阅读0次

    什么是ClassLoader?

    ClassLoader是类的加载器, 负责将.class文件装载进系统, 交给虚拟机进行连接, 初始化等操作(在jdk中是一个抽象类java.lang.ClassLoader)

    ClassLoader的种类

    • BootStrapClassLoader: C++编写, 加载核心库 java.*
    • sun.misc.Launcher$ExtClassLoader: Java编写, 加载扩展库 javax.*
      System.getProperty("java.ext.dirs"); 获取加载类的目录
    • sun.misc.Launcher$AppClassLoader: Java编写, 加载程序所在的目录(classpath)
      System.getProperty("java.class.path"); 获取加载类的目录
    • 自定义ClassLoader: Java编写

    自定义的ClassLoader能够完成很多有意义的操作 如:
    通过网络二进制流加载类
    对敏感class加密,然后在自定义classLoader中解密
    也可以修改class 对字节码 做一些扩展

    public class MyClassLoader extends ClassLoader {
        private String path;
        private String classLoaderName;
    
        public MyClassLoader(String path, String classLoaderName) {
            this.path = path;
            this.classLoaderName = classLoaderName;
        }
    
        /**
          *用于寻找类文件, 抽象类ClassLoader中的findClass方法不做任何事, 
          *直接抛出ClassNotFoundException, 所以要重写这个方法
          */
        @Override
        public Class findClass(String name) {
            byte[] b = loadClassData(name);
            return defineClass(name, b, 0, b.length);
        }
    
        //用于加载类文件
        private byte[] loadClassData(String name) {
            name = path + name + ".class";
            InputStream in = null;
            ByteArrayOutputStream out = null;
            try {
                in = new FileInputStream(new File(name));
                out = new ByteArrayOutputStream();
                int i = 0;
                while ((i = in.read()) != -1) {
                    out.write(i);
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    out.close();
                    in.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return out.toByteArray();
        }
    }
    
    

    这个自定义类加载器的作用是去指定的路径下加载指定名称的类

    使用

    public class ClassLoaderChecker {
        public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
            MyClassLoader m = new MyClassLoader("/Users/baidu/Desktop/", "myClassLoader");
            Class c = m.loadClass("HelloWorld");
            System.out.println(c.getClassLoader());
            System.out.println(c.getClassLoader().getParent());
            System.out.println(c.getClassLoader().getParent().getParent());
            System.out.println(c.getClassLoader().getParent().getParent().getParent());
            c.newInstance();
        }
    }
    

    类加载器的双亲委派机制

    使用委托机制是为了防止多份同样字节码的加载, 节省内存

    具体流程可以分析ClassLoader#loadClass(String name, boolean resolve)方法

    protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
        {
            synchronized (getClassLoadingLock(name)) {
                // First, check if the class has already been loaded
                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) {
                        // ClassNotFoundException thrown if class not found
                        // from the non-null parent class loader
                    }
    
                    if (c == null) {
                        // If still not found, then invoke findClass in order
                        // to find the class.
                        long t1 = System.nanoTime();
                        c = findClass(name);
    
                        // this is the defining class loader; record the stats
                        sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                        sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                        sun.misc.PerfCounter.getFindClasses().increment();
                    }
                }
                if (resolve) {
                    // 链接
                    resolveClass(c);
                }
                return c;
            }
        }
    

    逻辑还是比较简单清晰的, 先加个锁, 然后检查当前类加载器有没有加载过这个类, 如果没有加载过且父加载器不为空则向上委托给父类, 一直到调用BootStrapClassLoader
    如果仍然没有找到则调用加载器自身的findClass

    加载流程 如图所示 C8C923CA-3B18-4562-BF5A-5319AAB0A769.png

    类的加载过程

    1. 加载
    • 通过ClassLoader加载class文件字节码, 生成Class对象
    1. 链接(ClassLoader#resolveClass()方法)
    • 校验: 检查正确性与安全性
    • 准备: 为类分配存储空间并设置类变量与初始值
    • 解析: JVM将常量池内的符号引用转换为直接使用
    1. 初始化
    • 执行类变量赋值和静态代码块

    类的加载方式

    • 隐式加载: new
    • 显示加载: loadClass, forName

    loadClass 与 forName的区别

        // ClassLoader
        public Class<?> loadClass(String name) throws ClassNotFoundException {
            return loadClass(name, false);
        }
    

    通过分析源码可以知道

    • ClassLoader. loadClass 得到的class是还没有链接的
    • Class.forName 得到的是已经初始化完成的
      使用ClassLoader 不会执行静态代码块, forName会执行

    当类不需要加载静态代码块时, 可以用loadClass 加快执行速度, 即LazyLoader, 当需要用静态代码块加载一些Bean或驱动时, 则需要使用forName (如Mysql数据库驱动的加载)

    相关文章

      网友评论

          本文标题:类加载器 ClassLoader

          本文链接:https://www.haomeiwen.com/subject/giiyuqtx.html