1.1 Java类加载器基础
在深入了解 Tomcat的类加载器机制 之前,我们需要先回顾 Java类加载器(ClassLoader) 的基本原理。
类加载器的作用 :
在JVM中,类的字节码文件(.class
)并不是一次性全部加载进内存的,而是由 类加载器 在需要时动态加载。类加载器的职责就是:
-
根据类的全限定名找到对应的字节码文件;
-
将字节码转换为
Class
对象; -
缓存加载过的类,避免重复加载。
JVM内置的类加载器层次:
-
Bootstrap ClassLoader :最顶层,由C++实现,加载
rt.jar
等核心类库(java.lang.*
、java.util.*
)。 -
Extension ClassLoader :加载
JAVA_HOME/lib/ext
目录下的扩展包。 -
System ClassLoader(AppClassLoader) :加载
classpath
下的用户类。
这三者之间遵循 双亲委派模型:
-
一个类加载请求会 先交给父加载器 处理;
-
父加载器如果无法加载,才由当前加载器尝试。
👉 类比:就像你去快递站取包裹,前台会先让上级仓库查一遍,如果没有,才在自己仓库里找。
1.2 Tomcat类加载器层级结构
Tomcat是一个Web容器,它不仅需要加载自身的类库,还需要为每个Web应用加载 隔离的类空间。因此,Tomcat在JVM默认类加载器基础上,构建了自己的层次结构。
Tomcat主要的类加载器有:
-
Common ClassLoader
-
加载
$CATALINA_HOME/lib
下的类库。 -
Tomcat自身和所有Web应用共享。
-
-
Catalina ClassLoader
- 加载 Tomcat 核心模块(如
org.apache.catalina.*
)。
- 加载 Tomcat 核心模块(如
-
Shared ClassLoader
- 用于多个Web应用共享的类库(默认与
Common
合并)。
- 用于多个Web应用共享的类库(默认与
-
WebappClassLoader
-
每个Web应用都有独立的一个实例,加载
WEB-INF/classes
与WEB-INF/lib
。 -
保证Web应用之间相互隔离。
-
层级关系示意图
Bootstrap ClassLoader
↓
System ClassLoader
↓
Common ClassLoader
↙ ↘
CatalinaCL SharedCL
↓
WebappClassLoader (per app)
特点:
-
保证Tomcat内核和Web应用解耦;
-
不同Web应用之间互不影响(避免Jar包冲突);
-
通过
Common → Shared → Webapp
的逐级传递,实现共享与隔离并存。
1.3 双亲委派模型与Tomcat的定制化实现
JVM默认机制
Java类加载器默认采用 严格的双亲委派模型 ,优先保证 核心类库的安全性与一致性 。例如,应用自己写的 java.lang.String
类永远不会被加载,因为会优先从 Bootstrap
找到官方的版本。
Tomcat的需求
但是在Web容器中,情况变得复杂:
-
每个Web应用可能依赖 不同版本的同一个库 (如
log4j
)。 -
Web应用可能需要 覆盖Tomcat内置类库(如JSP编译器依赖的EL实现)。
👉 如果完全遵循双亲委派,就会导致:
-
Web应用无法加载自己的依赖;
-
不同应用之间库冲突。
Tomcat的解决方案
Tomcat在 WebappClassLoaderBase
中 打破了部分双亲委派规则:
-
优先加载Web应用自身的类;
-
如果本地找不到,才委派给父加载器。
核心源码(Tomcat 10.1.7
):
@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (name.intern()) {
// 1. 已加载过?
Class<?> clazz = findLoadedClass0(name);
if (clazz != null) return clazz;
// 2. 先尝试在当前Webapp中查找
try {
clazz = findClass(name);
if (clazz != null) return clazz;
} catch (ClassNotFoundException ignored) {}
// 3. 找不到再委派父加载器
try {
clazz = getParent().loadClass(name);
if (clazz != null) return clazz;
} catch (ClassNotFoundException ignored) {}
throw new ClassNotFoundException(name);
}
}
👉 可以看到,Tomcat的逻辑是:
Web应用优先 → 父加载器兜底,从而在保证隔离性的同时,也提供灵活性。
1.4 热部署与热加载机制
Web开发中,我们常常会修改JSP文件或更新依赖Jar,Tomcat支持 热加载(Hot Reloading):
-
JSP更新 :Tomcat通过
JspServlet
自动检测文件变更并重新编译。 -
Web应用重新加载 :Tomcat会销毁旧的
WebappClassLoader
,并创建一个新的实例。
👉 原理:
-
当检测到
WEB-INF/classes
或WEB-INF/lib
发生变化时,Tomcat调用WebappLoader
的stop()
方法。 -
销毁旧的类加载器,释放相关资源(类缓存、Jar文件句柄)。
-
创建新的
WebappClassLoader
实例,重新加载类。
注意:由于类卸载受JVM GC机制影响,如果存在静态引用、线程未释放等情况,会导致 类加载器泄漏 (ClassLoader Leak
)。这也是Tomcat运维常见问题之一。
1.5 源码解析与实战案例
让我们结合源码走一遍流程(Tomcat 10.1.7
):
类加载器初始化
Tomcat启动时,会初始化 Catalina.createClassLoader()
:
protected ClassLoader createClassLoader(String name, ClassLoader parent) {
try {
return ClassLoaderFactory.createClassLoader(
new String[] { "lib" }, // 加载路径
parent
);
} catch (Throwable t) {
throw new RuntimeException("Cannot create class loader", t);
}
}
Web应用加载
每个 Context
(即Web应用)对应一个 WebappLoader
,其内部持有 WebappClassLoaderBase
:
WebappLoader loader = new WebappLoader();
context.setLoader(loader);
启动时,Tomcat会为该应用创建一个新的 WebappClassLoader
,负责加载 WEB-INF/lib
与 WEB-INF/classes
。
小结
这一章我们从 Java类加载器基础 出发,逐层分析了 Tomcat的类加载器体系 ,并结合源码展示了其如何在 双亲委派模型 上进行定制化改造,从而支持Web应用的隔离、共享与热加载。
核心要点:
-
Tomcat类加载器结构分层:
Common → Shared → Webapp
。 -
Tomcat打破了双亲委派,优先加载应用内类。
-
热部署依赖于销毁并重建
WebappClassLoader
。
思考题
-
为什么JVM设计时采用双亲委派模型?
-
如果Tomcat完全遵循双亲委派,会导致哪些问题?
-
类加载器泄漏通常是怎么产生的?在Tomcat运维中该如何避免?