基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

为什么 Tomcat 要打破 JVM 的双亲委派模型?

知识点图片

Tomcat 确实打破了 JVM 标准的“双亲委派模型”(Parent Delegation Model),但这主要发生在 Web 应用程序(WebApp) 的类加载层面。

简单来说,Tomcat 这样做是为了解决 Web 容器特有的 隔离性(Isolation)灵活性 问题。

以下是 Tomcat 打破双亲委派模型的四个核心原因:

1. 依赖版本隔离(最主要原因)

场景: 假设你在 Tomcat 中部署了两个 Web 应用:

  • 应用 A 使用了 Spring 4.0。
  • 应用 B 使用了 Spring 5.0。

如果遵循双亲委派:
类加载器会委托给父加载器加载 Spring jar 包。由于 JVM 中全限定名(包名+类名)相同的类只能被加载一次,那么先加载了 Spring 4,应用 B 就会报错;反之应用 A 会报错。

Tomcat 的做法:
每个 Web 应用都有自己独立的 WebAppClassLoader。当加载类时,它优先在自己的 WEB-INF/libWEB-INF/classes 下寻找,找不到才委托给父加载器。这样,应用 A 和应用 B 就可以各自拥有独立的 Spring 版本,互不干扰。

2. 服务器自身与应用的隔离

场景: Tomcat 作为一个 Java 程序,自身也依赖许多第三方库(如 commons-logging, servlet-api 等)。Web 应用可能也依赖了这些库,但版本不同。

如果遵循双亲委派:
如果 Web 应用的类加载委托给系统加载器(AppClassLoader),而 Tomcat 的类库也在系统路径下,那么 Web 应用可能会意外使用到 Tomcat 内部的类库版本,导致兼容性问题。或者,Web 应用可能会覆盖 Tomcat 自身的逻辑,导致服务器不稳定。

Tomcat 的做法:
Tomcat 使用 CatalinaClassLoader 加载服务器内部类,使用 WebAppClassLoader 加载应用类。通过打破双亲委派(优先加载应用内的),保证了 Web 应用可以使用自己捆绑的库,而不会被 Tomcat 服务器的库覆盖(或者反过来覆盖服务器的库)。

3. JSP 的热部署(Hot Swap)

场景: 在开发时,我们修改了 JSP 文件,希望不需要重启服务器就能看到效果。

原理: JSP 文件最终会被编译成 .class 文件(Servlet)。在 JVM 中,一个类一旦被加载,就无法被“卸载”或“替换”,除非卸载加载它的 ClassLoader。

Tomcat 的做法:
Tomcat 为每个 JSP 文件都分配了一个独立的类加载器(JasperLoader)。当检测到 JSP 文件被修改时,Tomcat 会直接丢弃旧的类加载器,创建一个新的类加载器来加载修改后的 JSP 类。这种机制天然就不适合双亲委派(因为双亲委派强调只要父加载器能加载,子加载器就别动,这会导致无法加载新的类版本)。

4. 共享公共库

虽然 Tomcat 打破了隔离性,但它也保留了共享机制。
场景: 如果有 10 个应用都使用标准的 Servlet API,没必要在内存里加载 10 份。

Tomcat 的做法:
Tomcat 引入了 CommonClassLoader。对于放在 tomcat/lib 下的 jar 包,遵循双亲委派机制。Web 应用优先找自己的,找不到再找 Common 的。这实现了“既能隔离(应用私有),又能共享(通用库)”。


总结:Tomcat 的类加载逻辑

为了实现上述目标,Tomcat 的 WebAppClassLoader 的加载逻辑(loadClass 方法)大致如下,这正是“打破”双亲委派的体现:

  1. 检查缓存: 看该类是否已经加载过。
  2. JVM 核心类检查(关键): 如果是 java.* 开头的类(如 java.lang.String),必须委托给 JVM 的 Bootstrap ClassLoader 加载。这是为了安全性,防止恶意代码覆盖核心库。
  3. 加载应用自身类(打破点): 尝试从 WEB-INF/classesWEB-INF/lib 加载。(注意:标准模型是先委托父类,这里是先自己加载)
  4. 委托父类: 如果自己没找到,才委托给父类加载器(CommonClassLoader)去加载。

一句话总结: Tomcat 打破双亲委派是为了实现 “Web 应用之间的类库版本隔离” 以及 “Web 应用与服务器运行时的隔离”,同时保证核心 Java 类库的安全。

00:00
00:00