美文网首页程序员Android开发经验谈
Java的ClassLoader,你都懂了吗

Java的ClassLoader,你都懂了吗

作者: 好重 | 来源:发表于2020-01-06 22:01 被阅读0次

    类是Java程序的组成元素,Java中的每个类都有一个Class对象,为了生成这个Class对象,JVM会使用被称为“类加载器”的子系统,这些“类加载器”就是本文将描述的ClassLoader。

    Java并不是一开始就加载所有类,而是用到的时候才加载。当类的一个静态成员被引用时,这个类就会被加载。虽然类的构造函数没有用static修饰,但其实它也是类的静态方法。因此使用new操作符创建类的新对象也会被当做对类的静态成员的引用。

    类的加载使用的是被称为类加载器(ClassLoader)的子系统,它的形式是一个链条,链条的末端是原生类加载器。这个链条是这样的:

    自定义加载器(可选) -> AppClassLoader-> ExtClassLoader -> BootstrapClassLoader

    当需要加载一个类时,JVM会从这个类加载器链的头部开始,依次判断该类是否被加载(即是否能找到该类的Class对象),如果所有ClassLoader都没有加载过该类,就会从这个链条的末端往前开始加载这个类,如果某个类加载器加载成功,那它便是这个类的类加载器,即通过getClass().getClassLoader()方法将返回这个类加载器。如果所有加载器都无法找到这个类,那就会抛出ClassNotFoundException异常。这种加载机制我们称之为“双亲委托”

    自定义类加载器不是必须的,应该说一般情况下不会用到,除非有特殊的需求比如Android的插件化,或者支持从网络下载类等,那么另外三个类加载器都是什么用途呢?

    1. BootstrapClassLoader是JVM唯一个原生类加载器,它是JVM实现的一部分,原生类加载器加载的是可信类,包括Java API类,他们通常是从本地磁盘加载的。它查找类的路径可以通过System.getProperty("sun.boot.class.path")获取,输出结果:

    JAVA_HOME\jre\lib\resources.jar
    JAVA_HOME\jre\lib\rt.jar
    JAVA_HOME\jre\lib\sunrsasign.jar;
    ...

    1. ExtClassLoader是扩展的类加载器,它查找类的路径可以通过System.getProperty("java.ext.dirs")获取,输出结果:

    JAVA_HOME\jre\lib\ext
    C:\Windows\Sun\Java\lib\ext

    1. AppClassLoader负责加载应用程序自身的类,它查找类的路径包括应用程序的本地目录和其它目录,可以通过System.getProperty("java.class.path")查看:

    JAVA_HOME\jre\lib\charsets.jar
    JAVA_HOME\jre\lib\deploy.jar
    JAVA_HOME\jre\lib\ext\access-bridge-64.jar
    JAVA_HOME\jre\lib\ext\cldrdata.jar
    JAVA_HOME\jre\lib\resources.jar
    JAVA_HOME\jre\lib\rt.jar
    D:\Develop\TestRegex\out\production\TestRegex (当前目录)
    D:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2017.2.5\lib\idea_rt.jar
    ...

    Android插件化中,我们会用DexClassLoader加载一个插件的入口类,但插件包里有大量的类,需要一个个去调用ClassLoader.loadClass()吗?加载了一个类A,那A引用的其它类是怎么加载的?

    如果类A引用了类B,无论是直接引用还是通过class.forName()引用,JVM会找到类A的ClassLoader,再用这个ClassLoader去加载类B,加载的过程同样采用“双亲委托”机制,因此最终加载了类B的不一定是A的ClassLoader,有可能是AppClassLoader、ExtClassLoader或者是BootstrapClassLoader,而在Android系统中,则有可能是java.lang.BootClassLoader。因此,在运行的时候,JVM会动态地使用正确的类加载器逐一加载各个用到的类。

    加载类除了用ClassLoader的loadClass()之外,还可以用Class.forName()方法,它们有什么区别呢?

    主要有两个区别:

    1. Class.forName()默认使用当前类的ClassLoader加载,除非调用Class.forName(name, initialize, classlaoder)的方法来指定ClassLoader;
    2. Class.forName()会初始化对象,而ClassLoader.load()则不会。
      比如对于一个类A:
    public class A {
      static { System.out.println("time = " + System.currentTimeMillis()); }
    }
    

    以及以下两种方式加载这个类:

    public class Main1 {
      public static void main(String... args) throws Throwable {
        final Class<?> c = Class.forName("A");
      }
    }
    
    public class Main2 {
      public static void main(String... args) throws Throwable {
        ClassLoader.getSystemClassLoader().loadClass("A");
      }
    }
    

    Main1会输出time = 1313614183558,而Main2不会。除非调用了该类的某个静态方法,否则类不会被初始化。另外,Class.forName(name, initialize, classLoader)的第二个参数可以控制类是否执行初始化。

    参考文献

    [1] Difference between Loading a class using ClassLoader and Class.forName

    本文完。

    相关文章

      网友评论

        本文标题:Java的ClassLoader,你都懂了吗

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