Android ClassLoader解析(1) - Java ClassLoader

ClassLoader 即类加载器,其作用是在JVM虚拟机中动态的加载class文件。众所周知,我们写的Java应用程序都是由不同的 .class 文件组成的,当运行某段逻辑功能时,就会由一个class文件中的方法调用另一个class文件的方法,若此时调用的class文件找不到就会出现异常。而JVM启动时,并不会将所有的class文件一次全部加载进来,而是会根据需要动态的加载,只有class文件被加载到内存后,才能被其他class文件引用,这个过程中ClassLoader发挥着重要的作用。

一. Class文件

还记得最开始学习Java语言的时候,推荐的方法是先不要过度依赖IDE,而是使用类似记事本的文本编辑器来写的,步骤如下:

  1. 新建文件,文件格式是.java,编写代码逻辑,eg: HelloWorld.java;
  2. 编译.java文件,生成.class文件,命令:javac HelloWorld.java;
  3. 运行代码 命令:java HelloWorld,此时可以运行我们写的代码逻辑;

其实这个才是我们最开始学习的Java文件编译运行方式,只是习惯使用IDE后,IDE帮助我们做了很多工作。正常的.java格式文件是不能直接运行的,JVM无法识别,而.class文件是字节码格式文件,JVM可识别,而加载.class文件使JVM可识别,就是依靠ClassLoader。

二. Java默认的ClassLoader

  1. 启动类加载器:Bootstrap ClassLoader,主要加载JDK中的核心类库,如目录%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等;
  2. 扩展类加载器:Extention ClassLoader,主要加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件;
  3. 应用类加载器:App ClassLoader,加载当前应用的classpath的所有类;

三. 加载过程

1.加载顺序:

(1)Bootstrap ClassLoader;
(2)Extention ClassLoader;
(3)App ClassLoader;

2.源码分析

且通过查看源码,即JVM入口类sun.misc.Launcher,其中有BootstrapClassLoader、ExtClassLoader 和 AppClassLoader 的初始化代码,他们实际上是通过查阅环境属性来加载资源文件的,其对应关系为:

  • BootstrapClassLoader:sun.boot.class.path
  • ExtClassLoader: java.ext.dirs
  • AppClassLoader: java.class.path

可以通过获取相应的环境属性来输出路径内容,如

System.out.println(System.getProperty("java.class.path"));
3.每个类加载器都有一个父加载器
  • 一个ClassLoader创建时如果没有指定parent,那么它的parent默认就是AppClassLoader;
  • AppClassLoader的parent是ExtClassLoader;
  • ExtClassLoader的parent是null;
  • Bootstrap没有父加载器,但是它却可以作为任何一个ClassLoader的父加载器(下面会有分析);
4.父加载器不是父类
Android ClassLoader解析(1) - Java ClassLoader_第1张图片
ClassLoader继承关系
  • ExtClassloader 和 AppClassLoader 都继承自java.lang.ClassLoader类;
  • 用户自定义的ClassLoader需要继承java.lang.ClassLoader类;
  • BootStrapClassLoader不继承自java.lang.ClassLoader,因为它不是一个Java类,已经嵌入在JVM内核中,是虚拟机的一部分;

四. 双亲委派

1.双亲委派机制

ClassLoader是使用双亲委派模型来搜索类的。委托是从下向上,查找过程是自上至下:


Android ClassLoader解析(1) - Java ClassLoader_第2张图片
委派过程

过程如下:
(1) 收到Class文件请求,首先委派给AppClassLoader,首先看是否有缓存,如果有,则返回;否则将请求委托给父加载器;
(2)父加载器递归执行第1步;
(3)最终达到顶级父加载器BootstrapClassLoader,如果也没有缓存,就去规定路径下sun.misc.boot.path下查找,找到则返回;没有则让子加载器去查找;
(4)ExtClassLoader在java.ext.dirs下查找,找到则返回;没有则让子加载器查找;
(5) AppClassLoader在java.class.path下查找,找到就返回;没有则让子类加载器查找;
(6)如果子类加载器也找不到,则抛出异常。

2.源码分析 loadClass()
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) {
                        //父加载器不为空则调用父加载器的loadClass
                        c = parent.loadClass(name, false);
                    } else {
                        //父加载器为空则调用Bootstrap Classloader
                        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();
                    //父加载器没有找到,则调用findclass
                    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()
                resolveClass(c);
            }
            return c;
        }
    }

代码解释了双亲委派机制,步骤如下:
(1) findLoadedClass方法来检查该类是否已经加载过;
(2)若没有加载过,则调用父类的loadClass方法;
(3)递归执行步骤2,查找该Class对象;
(4)若直到BootstrapClassLoader依然没有加载成功,则通过findClass查找;
(5)findClass在各自的对应路径下查找,若没有则让子加载器查找,直到找到;否则抛出异常;
(6)最后若找到Class对象,且参数resolve为true,调用resolveClass(Class)方法来生成最终的Class对象,且该方法为native方法;

注意1:上面有提到ExtClassLoader的父加载器通过代码执行,结果显示为null,但在实际向上委托时,系统会为其指定为BootstrapClassLoader,代码如下:

if (parent != null) {
    c = parent.loadClass(name, false);
} else {
    c = findBootstrapClassOrNull(name);
}

注意2:要自定义一个ClassLoader,建议覆盖findClass()方法,而不要直接改写loadClass()方法。(原因:JDK已在loadClass方法中实现了ClassLoader搜索类的算法,当在loadClass方法中搜索不到类时,loadClass方法就会调用findClass方法来搜索类,所以只需重写该方法即可。如没有特殊的要求,一般不建议重写loadClass搜索类的算法。)

3.为什么使用双亲委派机制
  • 避免重复加载,当父类加载过,就不用子类再加载一遍;
  • 安全原因,避免子类自定义的类动态定义Java核心API定义的类;
  • JVM在判定两个class是否相同时,不仅要判断两个类名是否相同,而且要判断是否由同一个类加载器实例加载的。只有两者同时满足的情况下,JVM才认为这两个class是相同的。

五. 自定义ClassLoader

Java中自带的三种ClassLoader包括 BootStrapClassLoader、ExtClassLoader和AppClassLoader,但他们都是加载指定路径下的jar或者class文件,如果想要突破路径限制,能够加载我们指定路径的文件,就需要自定义ClassLoader。在自定义ClassLoader中,比较重要的一个方法是defineClass(),能将class二进制内容转换成Class对象,如果不符合要求的会抛出各种异常。

自定义ClassLoader步骤
  1. 编写一个类继承自ClassLoader抽象类;
  2. 复写它的findClass()方法;
  3. 在findClass()方法中调用defineClass();

参考资料

https://blog.csdn.net/briblue/article/details/54973413
https://blog.csdn.net/xyang81/article/details/7292380

你可能感兴趣的:(Android ClassLoader解析(1) - Java ClassLoader)