Tomcat类加载器原理简单介绍
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.*
)。
Shared ClassLoader
用于多个Web应用共享的类库(默认与
Common
合并)。
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运维中该如何避免?