Tomcat 中有哪些类加载器?


面试考察点

  1. 体系认知:面试官想看你是否能把 Tomcat 的类加载器从上到下完整地列出来,而不是只记得一个 "自定义类加载器"。漏掉任何一层都说明理解不够系统。

  2. 职责区分:每个类加载器加载哪些目录下的类、对谁可见、为什么要这么设计,这些能不能讲清楚。特别是 CommonCatalinaShared 这三个容易混淆的,能不能区分开。

  3. 与 JVM 标准的关系:Tomcat 的类加载器体系是在 JVM 标准体系之上扩展的,你能不能说清楚哪些是 JVM 自带的、哪些是 Tomcat 自己加的。

核心答案

Tomcat 的类加载器可以分为 两大阵营、一共 8 种

JVM 标准类加载器(3 个)——Tomcat 没动它,原封不动:

类加载器加载路径说明
BootstrapClassLoaderJAVA_HOME/librt.jar 等核心类库)JVM 内置,C++ 实现,Java 代码里拿不到引用
ExtClassLoaderJAVA_HOME/lib/ext 目录JDK 扩展类加载器
AppClassLoaderCLASSPATH 路径加载 Tomcat 启动入口类(如 Bootstrap.java

Tomcat 自定义类加载器(5 个)——这是重点:

类加载器加载路径对谁可见
CommonClassLoader$CATALINA_HOME/libTomcat 内部 + 所有 Web 应用
CatalinaClassLoadercatalina.propertiesserver.loader仅 Tomcat 内部
SharedClassLoadercatalina.propertiesshared.loader所有 Web 应用(Tomcat 内部看不到)
WebAppClassLoaderWEB-INF/classes + WEB-INF/lib仅当前 Web 应用
JasperLoaderwork/Catalina/localhost/xxx/org/apache/jsp仅当前 JSP 编译后的类

深度解析

一、JVM 标准三层——地基

这三层是 JVM 自带的,严格遵循双亲委派。Tomcat 启动时,AppClassLoader 加载了 org.apache.catalina.startup.Bootstrap 这个类(Tomcat 的入口),然后 Tomcat 就开始创建自己的类加载器了。

所以 Tomcat 的类加载器体系是从 AppClassLoader 之后开始扩展的。

二、CommonClassLoader——公共枢纽

CommonClassLoader 是 Tomcat 自定义的第一个类加载器,也是整个 Tomcat 类加载体系的 "分水岭"——上面的三层是 JVM 的,从它开始是 Tomcat 自己的。

它加载 $CATALINA_HOME/lib 下所有 jar 包。像 servlet-api.jar 这种规范级别的 jar,必须由它来加载,这样 Tomcat 内部和所有 Web 应用都能用同一份 Servlet API。你也可以把公共类库(比如日志框架、工具包)放到这个目录下,所有应用共享。

它的父加载器是 AppClassLoader

三、CatalinaClassLoader——Tomcat 的私房钱

这个类加载器专门给 Tomcat 内部用的,Web 应用看不到它加载的类。设计意图是让 Tomcat 内部用的一些私有依赖不会 "泄漏" 给 Web 应用。

但实际用得不多。因为默认配置下 server.loader 是空的,CatalinaClassLoader 的加载路径和 CommonClassLoader 完全一样——说白了就是同一个对象。除非你在 catalina.properties 里专门配置了 server.loader,否则它不会独立存在。

四、SharedClassLoader——应用间的共享区

CatalinaClassLoader 正好相反——这个是给所有 Web 应用共享的,但 Tomcat 内部看不到。典型场景:多个 Web 应用都用的某个内部框架,但你不想让 Tomcat 自身也依赖它。

同样,默认配置下 shared.loader 也是空的,SharedClassLoader 也就是 CommonClassLoader 的马甲。一般除非你有一台 Tomcat 跑很多应用、且确实有共享类库的需求,否则不用管它。

五、WebAppClassLoader——应用隔离的关键

这是整个体系中最重要、也是面试官最关注的类加载器。

为什么说它最重要? 因为前面四个(CommonCatalinaShared、包括 JVM 标准的三层)在 Tomcat 实例中只有一个,但 WebAppClassLoader 有多少个 Web 应用就有多少个。应用 A 有一个,应用 B 有一个,互相独立、互相隔离。

它的加载策略比较特别——对非核心类,先从自己的 WEB-INF 目录找,找不到再委托给父加载器。这个顺序和标准双亲委派是反过来的。为什么要反着来?因为要让 Web 应用优先使用自己带的类库版本。比如你的应用用了 Spring 5.3,Tomcat lib 下有 Spring 4.x,如果先问父加载器就会加载到旧版本。

java.*javax.* 这些核心类还是老老实实交给父加载器,不允许自己搞。安全底线不能破。

六、JasperLoader——JSP 热编译的秘密武器

JasperLoader 是生命周期最短的一个——每个 JSP 文件对应一个 JasperLoader。JSP 文件第一次被访问时,Tomcat 会把它编译成一个 Servlet 类(.class 文件),然后用 JasperLoader 加载。

当 JSP 文件被修改后,Tomcat 检测到文件变化,直接丢弃旧的 JasperLoader,创建一个新的重新编译。因为类加载器不同,JVM 认为新编译的类和旧的是不同的类型,不存在冲突。

这就是 JSP 热更新的原理——换类加载器,而不是替换类

七、完整的委托关系图

上图把所有类加载器的委托关系串起来了。整体记住一个方向:自下而上委托,WebApp 自己优先

两个关键点:

  • CatalinaClassLoaderSharedClassLoader 默认就是 CommonClassLoader,除非你专门配置了 server.loadershared.loader。面试时说 "Tomcat 默认情况下这三个实际上是同一个" 会加分。

  • 隔离发生的位置WebAppClassLoader 这一层。上面的 CommonClassLoader 只有一个实例,下面的每个应用各自有独立的 WebAppClassLoader + JasperLoader,隔离自然就有了。

面试高频追问

  1. 追问一:CommonClassLoaderCatalinaClassLoaderSharedClassLoader 默认是同一个对象吗?

    是的。默认配置下 catalina.properties 中的 server.loadershared.loader 都为空,这三个类加载器的加载路径完全一样,Tomcat 直接复用了同一个 CommonClassLoader 实例。只有你手动配置了不同的路径,它们才会各自独立。

  2. 追问二:一个 Tomcat 实例部署了 3 个应用,一共有多少个类加载器?

    JVM 标准层 3 个(BootstrapExtApp)+ Tomcat 层至少 3 个(CommonCatalinaShared,默认合为一体)+ 每个应用 1 个 WebAppClassLoader(3 个)+ 每个 JSP 文件 1 个 JasperLoader。不算 JSP 的话,最少是 7 个(默认配置下 Common/Catalina/Shared 合并为 1 个,就是 5 个)。

  3. 追问三:为什么 WebAppClassLoader 的父加载器是 CommonClassLoader 而不是 AppClassLoader

    因为 Web 应用的类不应该看到 AppClassLoader 加载的 Tomcat 启动类(比如 Bootstrap)。CommonClassLoader 作为 "公共桥梁",既让 Web 应用能访问公共类库(servlet-api 等),又屏蔽了 Tomcat 自身的启动实现细节。层次分明,各管各的。

常见面试变体

  • "Tomcat 的 WebAppClassLoader 和 JVM 的 AppClassLoader 有什么区别?"
  • "Tomcat 中 CommonClassLoaderSharedClassLoader 有什么区别?"
  • "说一说 Tomcat 的类加载器层次结构"

记忆口诀

三层 JVM 不动它,Common 管公共,Catalina 管 Tomcat 私有,Shared 管应用共享,WebApp 每应用一个管隔离,Jasper 每 JSP 一个管热编译。

从上到下记:BootstrapExtAppCommonCatalina/SharedWebAppJasper,一共 7 层,后 4 层是 Tomcat 自己加的。

总结

Tomcat 的类加载器体系是在 JVM 标准三层之上扩展了 4 层(CommonCatalinaSharedWebAppJasper)。CommonClassLoader 是公共枢纽,CatalinaClassLoaderSharedClassLoader 默认与它合体,WebAppClassLoader 是每应用一个实现隔离,JasperLoader 是每 JSP 一个实现热编译。面试时重点把 Common 的公共共享角色和 WebApp 的隔离 + "先自己后父亲" 策略讲清楚,基本就到位了。