类加载器和委托模型
什么是类的加载?
类的加载是指将类的 .class 文件中的二进制数据读入到内存中,再把它放到运行数据区的方法区内,然后在堆区创建一个 java.lang.Class 对象,用来封装类在方法区内的数据结构。
类的加载的最终目标是位于堆区中的 Class 对象,Class 对象封装了类在方法区内的数据结构,并且为 Java 程序员提供了访问方法区内的数据结构的接口。这个过程通过 Java 中的类加载器 ( ClassLoader ) 来完成。
对于 JVM 运行时的数据区的理解用一个图来显示很形象,下面的图显示 JVM 运行时的数据区:
![](https://img.haomeiwen.com/i4884083/7dd4df4dc32c7253.jpg)
类与类加载器
类加载器非常重要,因为每个类加载器都有一个独立的类名称空间。比如我们要加载两个类,如果要比较两个类是否相等(包括 equals() 方法、isAssignableFrom() 方法、isInstance() 方法),只有在这两个类被同一个类加载器加载的前提下比较才有意义。否则,即使两个类来自同一个 class 文件,被同一个 JVM 加载,但是加载它们的类加载器不同,那么这两个类不相等。
![](https://img.haomeiwen.com/i4884083/08f06d75b00e8f28.png)
类加载器的种类
站在 Java 虚拟机的角度来讲,ClassLoader 分为启动类加载器(Bootstrap ClassLoader)和其它的加载器。其中 Bootstrap ClassLoader 负责加载 Java 的核心类,由 JVM 实现 ( C++ ),而其它类加载器都由 Java 层实现并继承 java.lang.ClassLoader ,这些类加载器需要由启动类加载器加载到内存中之后才能去加载其他的类。
更细分的话,ClassLoader分为:
-
启动类加载器(Bootstrap ClassLoader)负责将 %JAVA_HOME%/jre/lib 目录中或 -Xbootclasspath 中参数指定的路径中的,并且能被虚拟机识别的类库加载到 JVM 中。启动类加载器是无法被 Java 程序直接引用的。
-
扩展类加载器(Extension ClassLoader)负责加载 %JAVA_HOME%/lib/ext 或者由 java.ext.dirs 系统变量指定的路径中的所有类库,开发者可以直接使用扩展类加载器。
-
应用程序类加载器(Application ClassLoader)负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
双亲委托模型
双亲委托模型
JVM中类加载的机制——双亲委托模型。这个模型要求除了 Bootstrap ClassLoader 外,其余的类加载器都要有自己的父加载器。子加载器通过组合来复用父加载器的代码,而不是使用继承。
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。
双亲委派模型最大的好处就是让Java类同其类加载器一起具备了一种带优先级的层次关系。这句话可能不好理解,我们举个例子。比如我们要加载顶层的 Java 类 —— java.lang.Object 类,无论我们用哪个类加载器去加载 Object 类,这个加载请求最终都会委托给 Bootstrap ClassLoader,这样就保证了所有加载器加载的 Object 类都是同一个类。
对一个 Class 对象来说,如果类加载器不同,即便是同一个字节码文件,生成的 Class 对象也是不等的。也就是说,类加载器相当于 Class 对象的一个命名空间。双亲委派机制则保证了基类都由相同的类加载器加载,这样就避免了同一个字节码文件被多次加载生成不同的 Class 对象的问题。