「JVM 类加载子系统」系列文章

破坏双亲委派模型

案例一:重写 loadClass 方法

通过分析源码可以知道双亲委派模型就是在loadClass中实现的,如果想打破,直接重写自己的loadClass逻辑即可

在前文 自定义类加载器 的基础上,重写loadClass方法,其他不变:

此时,就算不把类路径下对应的com.lfool.myself.Test03删掉,也会由自定义类加载器加载,而不会由应用程序类加载器加载

案例二:涉及 SPI 的加载

通过前文 双亲委派模型 介绍可以知道,JVM 使用「全盘委托机制」,但现在存在一个问题,如果有基础类需要回调用户的代码,怎么办???

假设基础类的类加载器是启动类加载器,根据「全盘委托机制」,该类所依赖及引用的类也由这个 CladdLoader 加载,但该类所引用的是用户代码,启动类加载器根本加载不了

所以这就涉及到父类加载器去请求子类加载器完成类加载行为,打通了双亲委派模型的层次结构来逆向使用类加载器,已经违背了双亲委派模型的一般性原则

案例三:热部署

热部署是指在项目不重启的前提下,更改部分内容,还能使项目顺利运行

OSGi 实现模块化热部署的关键是它自定义的类加载器机制的实现,每一个程序模块 (OSGi 中称为 Bundle) 都有一个自己的类加载器,当需要更换一个 Bundle 时,就把 Bundle 连同类加载器一起换掉以实现代码的热替换

在 OSGi 环境下,类加载器不再使用双亲委派模型推荐的树状结构,而是进一步发展为更为复杂的网状结构

案例四:Tomcat 部署多应用

前提

Tomcat 也是一个 Java 程序,启动一个 Tomcat 服务,首先创建一个 JVM 进程,然后 Tomcat 服务运行在 JVM 中

部署在 Tomcat 中的多个应用以线程的形式存在,每个应用都有自己单独的来加载器,所以应用与应用之间无法相互调用,实现了类的隔离

为什么要打破?

通常,我们会在一个tomcat下部署多个应用,而这多个应用可能使用的类库的版本是不同的

比如:项目 A 使用的是 Spring4,项目 B 使用的是 Spring5。Spring4 和 Spring5 多数类都是一样的,但是有个别类有所不同,这些不同是类的内容不同,而类名,包名都是一样的

假如,我们采用 JDK 向上委托的方式,项目 A 在部署的时候,应用类加载器加载了它的类;在部署项目 B 的时候,由于类名相同,这时应用服务器就不会再次加载同包同名的类,这样就会有问题

所以,Tomcat 需要打破双亲委派机制,不同的 war 包下的类自己加载,而不向上委托,但是基础类依然向上委托

如何打破?

7

上图中,蓝色部分依旧和原来一样,使用双亲委派模型;绿色部分是 Tomcat 第一部分自定义的类加载器,这一部分加载 Tomcat 包中的类,依旧采用双亲委派模型;紫色部分是 Tomcat 第二部分自定义的类加载器,正是这一部分,打破了双亲委派模型

Tomcat 第一部分自定义类加载器 (绿色部分)

在 Tomcat6 之前,这三个类加载器在三个不同的文件夹下;而 Tomcat6 及以后这三部分合并到了一个文件夹下

image-20221028193631736

下面介绍这三部分的作用:

这一部分类加载器,依然采用的是双亲委派机制。原因:它只有一份,如果有重复,那么也是以这一份为准,这部分主要加载的是 Tomcat 自带的类

Tomcat 第二部分自定义类加载器 (紫色部分)

紫色部分是项目在打成 war 包时,Tomcat 自动生成的类加载器,专门来加载这个 war 包,而这个类加载器打破了双亲委派模型

假设如果没有打破,一旦两个项目中有两个相通类名,但内容不同的类时,只会加载其中一个

自定义 Tomcat 的 war 包类加载器

前文 自定义类加载器 中介绍过如何自定义类加载器,该部分在前文的基础上,修改一丢丢即可

在两个不同的包中,有两个相同的类,关系如下:

两个类的内容如下:

对应两个不同的类加载器: