什么是双亲委派模型?
首先带着一个问题
为什么要是用双亲委派模型,如果自定义一个Object
会发生什么?
https://mp.weixin.qq.com/s/CCQW0vtr_XZJkjDRKU4jkQ
类加载过程
类加载过程可以分为三步: 加载 --> 连接 --> 初始化
而连接过程也可以分为三步:验证 --> 准备 --> 解析

加载是类加载过程的第一步,主要完成下面 3 件事情
- 通过全类名获取定义此类的二进制字节流
- 将字节流所代表的静态存储结构转换为方法区的运行时数据结构
- 在内存中生成一个代表该类的
Class
对象,作为方法区这些数据的访问入口
类加载器
类加载器是 Java
程序中的一个重要组成部分,赋予 Java
类可以被动态加载到 JVM
中并执行的能力
根据官方 API
的介绍
类加载器是一个负责加载类的对象
ClassLoader
是一个抽象类,给定类的二进制名称,类加载起应尝试定位或生成构成类定义的数据。典型的策略是将名称转换为文件名,然后从文件系统中读取该名称的类文件每个
Java
类都有一个引用指向加载它的ClassLoader
但是,数组类不是通过ClassLoader
创建的,而是通过JVM
在需要的时候自动创建的
数组类通过getClassLoader()
方法获取ClassLoader
的时候和该数组的元素类型的ClassLoader
是一致的
得出结论:
- 类加载器是一个负责加载类的对象,用于实现类加载过程中的 加载 这一步
- 每个
Java
类都有一个引用指向加载它的ClassLoader
- 数组类不是通过
ClassLoader
创建的,是由JVM
直接生成的
数组类没有对应的二进制字节流
简单来说,类加载器的主要作用就是加载 Java
类的字节码到 JVM
中
(. Class
文件 --> 内存中的代表该类的 Class
对象)
类加载规则
JVM
启动的时候,并不会一次性加载所有的类,而是根据需要去动态加载
也就是说,大部分类在具体用到的时候才会去加载,这样对内存更加友好
对于已经加载的类会被放在 ClassLoader
中
在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。也就是说,对于一个类加载器,相同二进制名称的类只会被加载一次
public abstract class ClassLoader {
...
private final ClassLoader parent;
// 由这个类加载器加载的类。
private final Vector<Class<?>> classes = new Vector<>();
// 由VM调用,用此类加载器记录每个已加载类。
void addClass(Class<?> c) {
classes.addElement(c);
}
...
}
类加载器总结
JVM
中内置了三个重要的 ClassLoader
BootstrapClassLoader
(启动类加载器)
最顶层的加载器,由C++
实现,通常表示为null
,并且没有父级,主要用于加载JDK
内部的核心类库ExtensionClassLoader
(扩展类加载器)
主要用于加载%JRE_HOME%/lib/ext
目录下的jar
包和类、被java.ext.dirs
系统变量所指定的路径下的所有类AppClassLoader
(应用程序类加载器)
面向用户的加载器,负责加载当前应用classpath
下的所有jar
包和类
除了这三种类加载器之外,用户还可以加入自定义的类加载器来进行拓展,以满足自己的特殊需求
比如,对 Java
类的字节码文件(.class
文件)进行加密,加载时再利用自定义的类加载器对其解密
除了 BootstrapClassLoader
是 JVM
自身的一部分之外,其他所有的类加载器都是在 JVM
外部实现的,并且全都继承自 ClassLoader
抽象类
用户可以自定义类加载器,以便让应用程序自己决定如何去获取所需的类
每个 ClassLoader
可以通过 getParent()
获取其父 ClassLoader
,如果获取到的 ClassLoader
为 null
的话,那么该类是通过 BootstrapClassLoader
加载的
因为 BootstrapClassLoader
是 C++
实现的,这个类在 Java
中是没有对应的类的
public abstract class ClassLoader {
...
// 父加载器
private final ClassLoader parent;
@CallerSensitive
public final ClassLoader getParent() {
//...
}
...
}
自定义类加载器
ClassLoader
类有两个关键的方法
protect Class loadClass(String name, boolean resolve)
加载指定二进制名称的类,实现了双亲委派机制name
为类的二进制名称,resolve
如果是true
,在加载时调用resolveClass(Class<?> c)
方法来解析该类protect Class findClass(String name)
根据类的二进制名称来查找类,默认实现是空方法
TIPS:
如果我们不想打破双亲委派模型,就重写 findClass
方法即可
无法被父亲加载器加载的类最终会通过这个方法被加载
双亲委派模型
类加载器有很多种,当我们想要加载一个类的时候,具体是哪个类加载器加载呢?
根据官网介绍
The ClassLoader class uses a delegation model to search for classes and resources. Each instance of ClassLoader has an associated parent class loader. When requested to find a class or resource, a ClassLoader instance will delegate the search for the class or resource to its parent class loader before attempting to find the class or resource itself. The virtual machine's built-in class loader, called the "bootstrap class loader", does not itself have a parent but may serve as the parent of a ClassLoader instance.
ClassLoader
类使用委托模型来搜索类和资源每个
ClassLoader
都有一个相关的父类加载器,需要查找类或者资源的时候,ClassLoader
实例会在视图亲自查找类或资源之前,将任务委托给父类加载器在虚拟机中,
BootstrapClassLoader
本身没有父类加载器,但是可以作为ClassLoader
实例的父类加载器
双亲委派模型并不是一种强制性的约束,只是 JDK
官方推荐的一种方式
另外,类加载器之前的父子关系不是以继承的关系来实现的,而是通常使用组合关系来复用父加载器的代码
(组合优于继承)
双亲委派模型的执行流程
实现代码的代码非常简单,逻辑非常清晰,都集中在 ClassLoader
的 loadClass
方法中
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
//首先,检查该类是否已经加载过
Class c = findLoadedClass(name);
if (c == null) {
//如果 c 为 null,则说明该类没有被加载过
long t0 = System.nanoTime();
try {
if (parent != null) {
//当父类的加载器不为空,则通过父类的loadClass来加载该类
c = parent.loadClass(name, false);
} else {
//当父类的加载器为空,则调用启动类加载器来加载该类
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
//非空父类的类加载器无法找到相应的类,则抛出异常
}
if (c == null) {
//当父类加载器无法加载时,则调用findClass方法来加载该类
//用户可通过覆写该方法,来自定义类加载器
long t1 = System.nanoTime();
c = findClass(name);
//用于统计类加载器相关的信息
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
//对类进行link操作
resolveClass(c);
}
return c;
}
}
每当一个类加载器接受到加载请求时,它会先将请求转发给父加载器。在父加载器没有找到所请求的类的情况下,该类加载器才会尝试去加载
执行流程如下:
- 在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载
- 类加载器在进行类加载的时候,它首先不会自己会尝试加载这个类,而是把这个请求委派给父类加载器去完成(调用父加载器的
loadClass()
),这样的话,所有的请求最终都会传送到顶层的启动类加载器BootstrapClassLoader
- 只有当父加载器反馈自己无法完成这个加载请求时(它的搜索范围中没有找到所需的类),子加载器才会尝试自己去加载(调用自己的
findClass()
方法来加载类)
冷知识:JVM
判断两个 Java
类是否相同不仅仅看类的全名是否相同,还要看加载此类的类加载器是否一样,只有两者都相同的情况下,才认为两个类是相同的
双亲委派模型的好处
主要是避免类的重复加载,保证 Java
的核心 API
不被篡改
如果没有使用双亲委派模型,而是每个类加载器加载自己的话就会出现一些问题!
比如我们自己编写一个叫做 java.lang.Object
的类,那么程序运行的时候,系统中就会出现两个不同的 Object
类。双亲委派模型可以保证加载的是 JRE
里的那个 Object
类,而不是自己写的 Object
类
因为 AppClassLoader
在加载你的 Object
类时,会委托给 ExtClassLoader
去加载,而 ExtClassLoader
又会委托给 BootstrapClassLoader
,BootstrapClassLoader
发现自己已经加载过了 Object
类,就会直接返回,不会去加载你写的 Object
类
打破双亲委派模型方法
自定义加载器的话,需要继承 ClassLoader
如果我们不想打破双亲委派模型,就重写 ClassLoader
类中的 findClass
方法,无法被父类加载的类最终会通过这个方法被加载
但是如果想打破双亲委派模型则需要重写 loadClass
方法了
在上文双亲委派模型的执行流程中已经解释了,类加载器在进行类加载的时候,它不会自己去尝试加载这个类,而是把这个请求委派给父亲加载器去完成