Tomcat 中有哪些类加载器?
面试考察点
-
体系认知:面试官想看你是否能把 Tomcat 的类加载器从上到下完整地列出来,而不是只记得一个 "自定义类加载器"。漏掉任何一层都说明理解不够系统。
-
职责区分:每个类加载器加载哪些目录下的类、对谁可见、为什么要这么设计,这些能不能讲清楚。特别是
Common、Catalina、Shared这三个容易混淆的,能不能区分开。 -
与 JVM 标准的关系:Tomcat 的类加载器体系是在 JVM 标准体系之上扩展的,你能不能说清楚哪些是 JVM 自带的、哪些是 Tomcat 自己加的。
核心答案
Tomcat 的类加载器可以分为 两大阵营、一共 8 种:
JVM 标准类加载器(3 个)——Tomcat 没动它,原封不动:
| 类加载器 | 加载路径 | 说明 |
|---|---|---|
BootstrapClassLoader | JAVA_HOME/lib(rt.jar 等核心类库) | JVM 内置,C++ 实现,Java 代码里拿不到引用 |
ExtClassLoader | JAVA_HOME/lib/ext 目录 | JDK 扩展类加载器 |
AppClassLoader | CLASSPATH 路径 | 加载 Tomcat 启动入口类(如 Bootstrap.java) |
Tomcat 自定义类加载器(5 个)——这是重点:
| 类加载器 | 加载路径 | 对谁可见 |
|---|---|---|
CommonClassLoader | $CATALINA_HOME/lib | Tomcat 内部 + 所有 Web 应用 |
CatalinaClassLoader | catalina.properties 的 server.loader | 仅 Tomcat 内部 |
SharedClassLoader | catalina.properties 的 shared.loader | 所有 Web 应用(Tomcat 内部看不到) |
WebAppClassLoader | WEB-INF/classes + WEB-INF/lib | 仅当前 Web 应用 |
JasperLoader | work/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——应用隔离的关键
这是整个体系中最重要、也是面试官最关注的类加载器。
为什么说它最重要? 因为前面四个(Common、Catalina、Shared、包括 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 自己优先。
两个关键点:
-
CatalinaClassLoader和SharedClassLoader默认就是CommonClassLoader,除非你专门配置了server.loader和shared.loader。面试时说 "Tomcat 默认情况下这三个实际上是同一个" 会加分。 -
隔离发生的位置在
WebAppClassLoader这一层。上面的CommonClassLoader只有一个实例,下面的每个应用各自有独立的WebAppClassLoader+JasperLoader,隔离自然就有了。
面试高频追问
-
追问一:
CommonClassLoader、CatalinaClassLoader、SharedClassLoader默认是同一个对象吗?是的。默认配置下
catalina.properties中的server.loader和shared.loader都为空,这三个类加载器的加载路径完全一样,Tomcat 直接复用了同一个CommonClassLoader实例。只有你手动配置了不同的路径,它们才会各自独立。 -
追问二:一个 Tomcat 实例部署了 3 个应用,一共有多少个类加载器?
JVM 标准层 3 个(
Bootstrap、Ext、App)+ Tomcat 层至少 3 个(Common、Catalina、Shared,默认合为一体)+ 每个应用 1 个WebAppClassLoader(3 个)+ 每个 JSP 文件 1 个JasperLoader。不算 JSP 的话,最少是 7 个(默认配置下Common/Catalina/Shared合并为 1 个,就是 5 个)。 -
追问三:为什么
WebAppClassLoader的父加载器是CommonClassLoader而不是AppClassLoader?因为 Web 应用的类不应该看到
AppClassLoader加载的 Tomcat 启动类(比如Bootstrap)。CommonClassLoader作为 "公共桥梁",既让 Web 应用能访问公共类库(servlet-api等),又屏蔽了 Tomcat 自身的启动实现细节。层次分明,各管各的。
常见面试变体
- "Tomcat 的
WebAppClassLoader和 JVM 的AppClassLoader有什么区别?" - "Tomcat 中
CommonClassLoader和SharedClassLoader有什么区别?" - "说一说 Tomcat 的类加载器层次结构"
记忆口诀
三层 JVM 不动它,Common 管公共,Catalina 管 Tomcat 私有,Shared 管应用共享,WebApp 每应用一个管隔离,Jasper 每 JSP 一个管热编译。
从上到下记:Bootstrap → Ext → App → Common → Catalina/Shared → WebApp → Jasper,一共 7 层,后 4 层是 Tomcat 自己加的。
总结
Tomcat 的类加载器体系是在 JVM 标准三层之上扩展了 4 层(Common、Catalina、Shared、WebApp、Jasper)。CommonClassLoader 是公共枢纽,CatalinaClassLoader 和 SharedClassLoader 默认与它合体,WebAppClassLoader 是每应用一个实现隔离,JasperLoader 是每 JSP 一个实现热编译。面试时重点把 Common 的公共共享角色和 WebApp 的隔离 + "先自己后父亲" 策略讲清楚,基本就到位了。
