Tomcat类加载器原理简单介绍

1.1 Java类加载器基础

在深入了解 Tomcat的类加载器机制 之前,我们需要先回顾 Java类加载器(ClassLoader) 的基本原理。

类加载器的作用

在JVM中,类的字节码文件(.class)并不是一次性全部加载进内存的,而是由 类加载器 在需要时动态加载。类加载器的职责就是:

  1. 根据类的全限定名找到对应的字节码文件;

  2. 将字节码转换为 Class 对象;

  3. 缓存加载过的类,避免重复加载。

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主要的类加载器有:

  1. Common ClassLoader

    • 加载 $CATALINA_HOME/lib 下的类库。

    • Tomcat自身和所有Web应用共享。

  2. Catalina ClassLoader

    • 加载 Tomcat 核心模块(如 org.apache.catalina.*)。
  3. Shared ClassLoader

    • 用于多个Web应用共享的类库(默认与 Common 合并)。
  4. WebappClassLoader

    • 每个Web应用都有独立的一个实例,加载 WEB-INF/classesWEB-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/classesWEB-INF/lib 发生变化时,Tomcat调用 WebappLoaderstop() 方法。

  • 销毁旧的类加载器,释放相关资源(类缓存、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/libWEB-INF/classes


小结

这一章我们从 Java类加载器基础 出发,逐层分析了 Tomcat的类加载器体系 ,并结合源码展示了其如何在 双亲委派模型 上进行定制化改造,从而支持Web应用的隔离、共享与热加载。

核心要点:

  1. Tomcat类加载器结构分层:Common → Shared → Webapp

  2. Tomcat打破了双亲委派,优先加载应用内类。

  3. 热部署依赖于销毁并重建 WebappClassLoader


思考题

  1. 为什么JVM设计时采用双亲委派模型?

  2. 如果Tomcat完全遵循双亲委派,会导致哪些问题?

  3. 类加载器泄漏通常是怎么产生的?在Tomcat运维中该如何避免?