开始介绍类加载器和双亲委派模型之前,简单回顾一下类加载过程。
- 类加载过程:加载->连接->初始化。
- 连接过程又可分为三步:验证->准备->解析。
加载是类加载过程的第一步,主要完成下面 3 件事情:
- 通过全类名获取定义此类的二进制字节流
- 将字节流所代表的静态存储结构转换为方法区的运行时数据结构
- 在内存中生成一个代表该类的
Class
对象,作为方法区这些数据的访问入口
类加载器是什么
加载器是 JVM 中负责加载类的组件。简单来说,它的作用就是将字节码文件(.class 文件)加载到 JVM 的方法区中,并在堆中创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类加载器是 Java 实现动态加载的关键,通过它,Java 程序可以在运行时动态地加载和使用类。
类加载器的分类
BootstrapClassLoader
(启动类加载器):最顶层的加载类,由 C++实现,通常表示为 null,并且没有父级,主要用来加载 JDK 内部的核心类库(%JAVA_HOME%/lib
目录下的rt.jar
、resources.jar
、charsets.jar
等 jar 包和类)以及被-Xbootclasspath
参数指定的路径下的所有类。ExtensionClassLoader
(扩展类加载器):主要负责加载%JRE_HOME%/lib/ext
目录下的 jar 包和类以及被java.ext.dirs
系统变量所指定的路径下的所有类。AppClassLoader
(应用程序类加载器):面向我们用户的加载器,负责加载当前应用 classpath 下的所有 jar 包和类。
除了这三种类加载器之外,用户还可以加入自定义的类加载器来进行拓展,以满足自己的特殊需求。就比如说,我们可以对 Java 类的字节码( .class
文件)进行加密,加载时再利用自定义的类加载器对其解密。
除了 BootstrapClassLoader
是 JVM 自身的一部分之外,其他所有的类加载器都是在 JVM 外部实现的,并且全都继承自 ClassLoader
抽象类。这样做的好处是用户可以自定义类加载器,以便让应用程序自己决定如何去获取所需的类。
而类加载器的体系不是继承关系的,而是委派体系,类加载器首先会到自己的父亲中查找类和资源,如果找不到才回到自己的本地进行查找。类加载器的委托行为动机是为了避免相同的类被多次加载
双亲委派模型
工作原理
双亲委派模型是一种类加载的层级关系模型。当一个类加载器收到类加载请求时,它首先不会自己去尝试加载这个类,而是把请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中。只有当父类加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子类加载器才会尝试自己去加载。
那么为什么会需要使用双亲委派模型呢?
通过双亲委派机制可以避免某一个类被重复加载,当父类已经加载后则无需重复加载,保证唯一性
为了安全,保证类库API不会被修改
这里解释一下如何保证类库的API不会被修改:当我们创建一个类String的时候,由于在Java中本身就存在String类,所以使用双亲委派模型的时候,在启动类加载器就会加载Java中的String类,而不会使用应用类加载器进行加载。
public class String {
public static void main(String[] args) {
System.out.println("demo info")
}
}
此时执行main函数,会出现异常,在类 java.lang.String 中找不到 main 方法
出现该信息是因为由双亲委派的机制,java.lang.String的在启动类加载器 (Bootstrap classLoader)得到加载,因为在核心jre库中有其相同名字的类文件, 但该类中并没有main方法。这样就能防止恶意篡改核心API库
优势
避免类的重复加载:因为双亲委派模型保证了类只会被加载一次,当父类加载器已经加载过某个类时,子类加载器就不会再次加载,节省了系统资源。
保证核心类库的安全性:核心类库由启动类加载器加载,不会被用户自定义的类加载器随意替换,避免了恶意代码对核心类库的篡改。