Java的类加载器体系以及Tomcat为何打破双亲委派模型,是理解Java类隔离机制和Web容器设计的关键。下面分别进行解释。
一、Java 类加载器与双亲委派模型
1. 类加载器层次结构
Java 默认提供了三个主要类加载器:
- 启动类加载器(Bootstrap ClassLoader) :C++实现,负责加载
<JAVA_HOME>/lib目录下的核心类库(如rt.jar),是所有类加载器的父类。 - 扩展类加载器(Extension ClassLoader) :Java实现,负责加载
<JAVA_HOME>/lib/ext目录下的扩展类库,父加载器为Bootstrap。 - 应用类加载器(Application ClassLoader) :也称系统类加载器,负责加载用户类路径(ClassPath)下的类,父加载器为Extension。
2. 双亲委派模型的工作机制
当一个类加载器收到类加载请求时,它会先委托父加载器去加载 ,如果父加载器无法加载(抛出ClassNotFoundException),才由自己尝试加载。流程如下:
text
请求 → 当前加载器 → 委派给父加载器 → ... → 最终到达Bootstrap
→ 若Bootstrap无法加载 → 逐级回退,由子加载器尝试加载
3. 为什么采用双亲委派?
- 安全性 :防止用户自定义的类替换核心API(如
java.lang.Object)。因为双亲委派会优先让Bootstrap加载,保证了核心类库的统一性。 - 避免重复加载:确保同一个类在全系统中只被加载一次。
4. 加载过程?
类加载器负责将.class文件加载到JVM中,整个过程包括 加载(Loading)→ 连接(Linking)→ 初始化(Initialization) 三个主要阶段,其中连接又细分为 验证(Verification)→ 准备(Preparation)→ 解析(Resolution)
二、Tomcat 为什么要打破双亲委派?
Tomcat作为Web容器,需要同时部署多个Web应用,每个应用可能依赖不同版本的第三方库(如A应用使用Spring 4,B应用使用Spring 5),同时还要保证这些应用之间完全隔离 ,并且共享一些基础类库(如Servlet API、JSP API等)。如果完全遵循双亲委派,会导致以下问题:
- 无法隔离应用:在双亲委派下,所有应用的类都会由相同的AppClassLoader或其父加载器加载,不同应用的同名类(但版本不同)会发生冲突。
- 无法实现热部署:当重新部署一个应用时,需要卸载该应用的类。双亲委派中类一旦被父加载器加载,就无法被卸载。
因此,Tomcat必须打破双亲委派,自定义类加载器来满足Web容器的特殊需求。
三、Tomcat 的类加载器体系与打破机制
Tomcat设计了一套层次化的类加载器结构(以Tomcat 8为例):
text
scss
Bootstrap
│
└── System (AppClassLoader)
│
└── Common
├── Catalina
└── Shared
└── WebApp (每个Web应用一个实例)
- Common :加载
$CATALINA_BASE/lib下的公共类库,供Tomcat服务器和所有Web应用共享。 - Catalina :加载Tomcat服务器内部实现所需的类(如
catalina.jar),对Web应用不可见。 - Shared:加载所有Web应用共享的类库(如自定义的公共库),对Tomcat服务器内部不可见。
- WebApp :每个Web应用拥有独立的WebAppClassLoader,负责加载
/WEB-INF/classes和/WEB-INF/lib下的类。
Tomcat 如何打破双亲委派?
WebAppClassLoader 打破了传统的双亲委派顺序:它首先尝试自己加载 (即加载/WEB-INF/classes和/WEB-INF/lib下的类),如果找不到,才委托父加载器(通常是Shared)加载。这样做的目的是:
- 优先加载应用自身的类,实现应用之间的隔离。如果A应用和B应用有同名但不同版本的类,各自的WebAppClassLoader会独立加载,互不影响。
- 仍允许共享某些基础类:当应用自身的类加载不到时,仍然可以向上委托,让Common或System加载JDK和Tomcat的基础类(如Servlet API),避免重复加载。
这种策略可以总结为:先自加载,后委派,与标准的双亲委派(先委派后自加载)正好相反。
其他打破点
- Catalina 和 Shared 也打破了双亲委派吗?它们通常遵循标准的双亲委派,因为Tomcat需要确保内部实现(Catalina)不会被Web应用意外覆盖。只有WebAppClassLoader是主要打破点。
四、为什么Tomcat可以打破双亲委派?
双亲委派模型是Java官方推荐的实现,但并非强制约束 。ClassLoader类允许子类重写loadClass()方法,从而改变加载顺序。Tomcat正是通过自定义类加载器(WebappClassLoader)重写了loadClass(),实现了优先自加载的逻辑。
五、总结
| 对比点 | 标准双亲委派 | Tomcat WebAppClassLoader |
|---|---|---|
| 加载顺序 | 先委托父类,后自加载 | 先自加载,后委托父类 |
| 目的 | 安全性、避免重复加载 | 应用隔离、支持热部署 |
| 类冲突 | 不允许同名类存在 | 允许不同应用有同名不同版本类 |
Tomcat打破双亲委派,本质是在隔离性与共享性之间找到平衡:既要让每个Web应用拥有独立的类空间,又要保证基础类库(如JDK、Servlet API)被所有应用共享,从而节省内存、避免冲突。这一设计也成为了其他Java Web容器(如Jetty、Undertow)的通用实践。