Bootstrap

【JVM】Tomcat 的类加载机制

Tomcat 是一个开源的 Java Servlet 容器,用于运行 Java Web 应用程序。它的类加载机制相对复杂,因为它需要在支持多种应用的同时保持隔离性和灵活性。以下是 Tomcat 类加载机制的详细描述。

Tomcat 类加载器的层次结构

Tomcat 采用了一种层次化的类加载器结构,以便在不同的应用程序之间实现类加载的隔离,同时也允许共享一些公共类库。Tomcat 的类加载器层次结构如下:

  1. Bootstrap ClassLoader

    • 这是 JVM 自带的类加载器,负责加载 Java 核心类库(如 rt.jar)。
  2. System ClassLoader

    • 也称为应用程序类加载器,加载 $JAVA_HOME/lib/extclasspath 中的类。
  3. Common ClassLoader

    • 由 Tomcat 自定义,加载 CATALINA_HOME/libCATALINA_BASE/lib 中的类库。
  4. Catalina ClassLoader

    • 加载 CATALINA_HOME/libCATALINA_BASE/lib 中的类库,通常与 Common ClassLoader 合并使用。
  5. Shared ClassLoader

    • 加载 CATALINA_BASE/shared/lib 中的类库,可供所有 Web 应用程序共享。
  6. Webapp ClassLoader

    • 每个 Web 应用都有自己的类加载器,加载 WEB-INF/classesWEB-INF/lib 中的类库。Tomcat 为每个 Web 应用程序创建一个新的 Webapp ClassLoader 实例。

类加载过程

  1. Bootstrap ClassLoader 首先加载 Java 核心类库。
  2. System ClassLoader 加载系统级别的类库。
  3. Common ClassLoader 加载 Tomcat 公共库。
  4. Catalina ClassLoader 加载与 Tomcat 运行时相关的类库。
  5. Shared ClassLoader 加载所有 Web 应用程序共享的类库。
  6. Webapp ClassLoader 最后加载各自应用程序的类库。此类加载器是独立的,确保不同 Web 应用程序之间的类隔离。

Webapp ClassLoader 的双亲委派模型

Webapp ClassLoader 使用双亲委派模型,但其委派顺序有所不同。一般的双亲委派模型是:子 -> 父 -> 根。而 Tomcat 的 Webapp ClassLoader 的委派顺序是:子 -> 父 -> 共享 -> 公共 -> 根。这是为了确保 Web 应用程序首先加载自己的类,而不是公共类库中的类。

典型的类加载顺序

当一个 Web 应用请求加载一个类时,Tomcat 的类加载器会按照以下顺序尝试加载:

  1. Webapp ClassLoader:首先检查 Web 应用的 WEB-INF/classesWEB-INF/lib 中是否有该类。
  2. Shared ClassLoader:如果 Webapp ClassLoader 没有找到,则委派给 Shared ClassLoader。
  3. Common ClassLoader:如果 Shared ClassLoader 也没有找到,则委派给 Common ClassLoader。
  4. System ClassLoader:如果 Common ClassLoader 也没有找到,则委派给 System ClassLoader。
  5. Bootstrap ClassLoader:最后由 Bootstrap ClassLoader 尝试加载。

破坏双亲委派模型

在某些情况下,可能需要打破双亲委派模型,以实现某些特定的功能。Tomcat 提供了几种方式:

  1. 自定义类加载器:通过创建自定义的类加载器,可以在特定的类加载场景中打破双亲委派模型。
  2. 使用server.xml配置文件:可以在 server.xml 文件中配置 <Loader> 元素,自定义 Web 应用的类加载器行为。
  3. 在 Web 应用中显式地使用不同的类加载器:例如,通过使用 Thread.currentThread().getContextClassLoader() 来获取当前线程的类加载器,并在应用代码中显式地使用它加载类。

示例:自定义 Web 应用类加载器

以下是一个简单的自定义 Web 应用类加载器的示例,展示了如何在 Tomcat 中打破双亲委派模型:

package com.example;

import java.net.URL;
import java.net.URLClassLoader;

public class CustomWebappClassLoader extends URLClassLoader {
    
    public CustomWebappClassLoader(URL[] urls, ClassLoader parent) {
        super(urls, parent);
    }

    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        // 打破双亲委派模型,首先尝试加载自己定义的类
        synchronized (getClassLoadingLock(name)) {
            // 检查是否已经加载过该类
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                try {
                    // 尝试从当前类加载器中加载
                    c = findClass(name);
                } catch (ClassNotFoundException e) {
                    // 如果当前类加载器无法加载,则委派给父类加载器
                    c = super.loadClass(name, resolve);
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
}

context.xmlserver.xml 中配置自定义类加载器:

<Context path="/myapp" docBase="webapps/myapp" reloadable="true">
    <Loader loaderClass="com.example.CustomWebappClassLoader"/>
</Context>

总结

Tomcat 的类加载机制通过层次化的类加载器结构和双亲委派模型,确保了类加载的隔离性和安全性。然而,在某些特殊情况下,可能需要打破双亲委派模型,以实现特定的功能或需求。通过自定义类加载器和配置文件,开发者可以灵活地控制类加载行为。

;