美文网首页
类加载器 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