跳至主要內容

五、类的加载篇——双亲委派机制

安图新大约 5 分钟

五、类的加载篇——双亲委派机制

一、定义

定义

如果一个类加载器在接到加载类的请求时,它首先不会自己尝试去加载这个类,而是把这个请求任务委托给父类加载器去完成,依次递归,如果父类加载器可以完成类加载任务,就成功返回。只有父类加载器无法完成此加载任务时,才自己去加载。

工作原理

1、 如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行;;
2、 如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器;;
3、 如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式;

 
 

二、如何证明、源码分析

证明

双亲委派机制在 java.lang.ClassLoader.loadClass(String name, boolean resolve)接口中体现。该接口的逻辑如下:

1、 先在当前加载器的缓存中查找有无目标类,如果有,直接返回;
2、 判断当前加载器的父加载器是否为空,如果不为空,则调用 parent.loadClass(name,false)接口进行加载;
3、 反之,如果当前加载器的父类加载器为空,则调用 findBootstrapClassOrNull(name)接口,让引导类加载器进行加载;
4、 如果通过以上 3 条路径都没能成功加载,则调用 findClass(name)接口进行加载该接口最终会调用;

java.lang.ClassLoader 接口的 defineClass 系列的 native 接口加载目标 Java 类。 双亲委派的模型就隐藏在这第 2 和第 3 步中。

举例

假设当前加载的是 java.lang.Object 这个类,很显然,该类属于 JDK 中核心得不能再核心的一个类,因此一定只能由引导类加载器进行加载。当 JVM 准备加载 java.lang.Object 时,JVM 默认会使用系统类加载器去加载,按照上面 4 步加载的逻辑,在第 1 步从系统类的缓存中肯定查找不到该类,于是进入第 2 步。由于从系统类加载器的父加载器是扩展类加载。

思考

如果在自定义的类加载器中重写 java.lang.ClassLoader.loadClass(String)或 java.lang.ClassLoader.loadClass(String, boolean)方法,抹去其中的双亲委派机制,仅保留上面这 4 步中的第 1 步与第 4 步,那么是不是就能够加载核心类库了呢?

这也不行!因为 JDK 还为核心类库提供了一层保护机制。不管是自定义的类加载器,还是系统类加载器抑或扩展类加载器,最终都必须调用 java.lang.ClassLoader.defineClass(String name, byte[] b, int off, int len, ProtectionDomain protectionDomain)方法,而该方法会执**行 preDefineClass(String name, ProtectionDomain pd)**接口,该接口中提供了对 JDK 核心类库的保护。

三、优势、劣势

双亲委派机制优势

1、 避免类的重复加载,确保一个类的全局唯一性;

**Java 类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,**当父亲已经加载了该类时,就没有必要子 ClassLoader 再加载一次。 2、 保护程序安全,防止核心 API 被随意篡改;

双亲委托模式的劣势

检查类是否加载的委托过程是单向的,这个方式虽然从结构上说比较清晰,使各个 ClassLoader 的职责非常明确,但是同时会带来一个问题,即顶层的 ClassLoader 无法访问底层的 ClassLoader 所加载的类。

四、破坏双亲委派机制及举例

为什么要破坏双亲委派机制?

为了解决顶层的 ClassLoader 无法访问底层的 ClassLoader 所加载的类的问题。

第一次破坏双亲委派机制

JDK1.2 之后。

第二次破坏双亲委派机制

线程上下文类加载器(Thread ContextClassLoader)。这个类加载器可以通过 java.lang.Thread 类的 setContextClassLoader()方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器。

第三次破坏双亲委派机制

第三次“被破坏”是由于用户对程序动态性的追求而导致的。如:代码热替换(Hot Swap)、模块热部署(Hot Deployment)等

1、热替换的实现

热替换是指在程序的运行过程中,不停止服务,只通过替换程序文件来修改程序的行为。热替换的关键需求在于服务不能中断,修改必须立即表现正在运行的系统之中。基本上大部分脚本语言都是天生支持热替换的,比如:PHP,只要替换了 PHP 源文件,这种改动就会立即生效,而无需重启 Web 服务器。

但对 Java 来说,热替换并非天生就支持,**如果一个类已经加载到系统中,通过修改类文件,并无法让系统再来加载并重定义这个类。**因此,在 Java 中实现这一功能的一个可行的方法就是灵活运用 ClassLoader。

注意:由不同 ClassLoader 加载的同名类属于不同的类型,不能相互转换和兼容。即两个不同的 ClassLoader 加载同一个类,在虚拟机内部,会认为这 2 个类是完全不同的。

根据这个特点,可以用来模拟热替换的实现,基本思路如下图所示:

 
 

2、Tomcat 的类的加载机制

 
 
上次编辑于:
贡献者: Andy