跳转到内容

Web 应用类加载隔离

Tomcat 必须允许多个应用使用不同版本依赖,同时共享 Servlet API 和容器公共类,因此类加载隔离是部署模型的一部分。

先给答案:Web 应用类加载器是在“共享容器能力”和“应用版本隔离”之间做选择

Section titled “先给答案:Web 应用类加载器是在“共享容器能力”和“应用版本隔离”之间做选择”

Tomcat 不能简单使用严格双亲委派,因为不同 Web 应用可能需要加载同名但不同版本的库;同时 Java 核心类和容器 API 又必须保持统一,不能被应用自己的 jar 覆盖。

因此加载顺序按类别区分:系统类、容器类优先,应用类在隔离范围内自行查找。代价是类加载问题更难排查:同名类来自哪个 loader、线程上下文类加载器是什么、卸载时是否仍有线程或静态引用,都可能决定热部署是否成功。

Bootstrap/JDK <- Common <- Catalina/Shared <- WebappClassLoader
WEB-INF/classes/lib

WebappClassLoaderBase#loadClass 位于 java/org/apache/catalina/loader/WebappClassLoaderBase.java:1132,Java 平台类处理在 :1198;停止逻辑从 :1387 开始。

替代方案:所有类都严格先父后子。 为什么不行:应用无法隔离冲突的第三方依赖。Tomcat 对 Java 平台类和容器 API 保持父优先,对普通应用类提供 child-first 能力,行为受 delegate 配置影响。

停止时需要清理 JDBC 驱动、线程、ThreadLocal、Timer 和静态引用,否则旧 ClassLoader 仍可能被 GC Root 保留。

场景 现象 原因 规避
API 被应用打包 ClassCastException 同名 API 来自不同 ClassLoader 不在 WAR 打包容器 API
热部署内存增长 旧 ClassLoader 不回收 线程或 ThreadLocal 持有旧类 关闭应用线程、清理资源
依赖冲突 NoSuchMethodError 实际版本不同 检查委派顺序和依赖树

插件系统可采用宿主 API 父优先、插件实现子优先、卸载前清理外部引用的设计。热卸载难点通常是线程和缓存引用,而非 ClassLoader 本身。

面试锚点

  • Tomcat 为什么打破严格双亲委派?
  • 哪些对象最容易造成 ClassLoader 泄漏?
  • 为什么同名类会 ClassCastException?