首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

深入剖析 Tomcat 筆記第 8 章:類加載器架構與 WebAppClassLoader 實現原理

     編輯:Java 綜合教學

深入剖析 Tomcat 筆記第 8 章:類加載器架構

Tomcat 作為 Servlet 容器,其類加載器體系打破了標準雙親委派模型,實現了 Web 應用間的類隔離、熱部署與庫版本共存。本章筆記梳理核心類加載器層級、委派邏輯與關鍵源碼流程。

一、Tomcat 類加載器層級圖譜

Bootstrap ClassLoader (C++ 實現)
    │  加載:$JAVA_HOME/jre/lib/rt.jar 等核心類
    ▼
System / Application ClassLoader
    │  加載:CLASSPATH、tomcat-bootstrap.jar、tomcat-juli.jar
    ▼
Common ClassLoader  (${catalina.home}/lib)
    │  加載:servlet-api.jar, jsp-api.jar, tomcat-dbcp.jar 等容器與應用共享庫
    ▼
┌─────────────────────────────────────┐
│  Catalina 内部類加載器 (私有)         │
│  ├─ Server ClassLoader              │  加載:catalina.jar 等容器內部實現類
│  └─ Shared ClassLoader              │  加載:${catalina.base}/shared/lib/*.jar (已廢棄)
└─────────────────────────────────────┘
    ▼
WebAppClassLoader (每個 WebApp 獨立實例)
    │  加載:/WEB-INF/classes、/WEB-INF/lib/*.jar
    │  特點:打破雙親委派,優先自行加載(WebApp 私有類),再委派父加載器
    ▼
JSP ClassLoader (每個 JSP 頁面一個實例,繼承 WebAppClassLoader)
    │  加載:JSP 編譯生成的 Servlet 類
    ▼

二、WebAppClassLoader 核心源碼邏輯 (Tomcat 7/8/9 一致)

// org.apache.catalina.loader.WebAppClassLoader
@Override
public Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
    // 1. 緩存檢查
    Class clazz = findLoadedClass0(name);
    if (clazz != null) return clazz;

    // 2. 【關鍵差異】優先檢查是否為系統/容器核心類 → 委派父加載器
    if (isSystemClass(name) || isContainerClass(name)) {
        return super.loadClass(name, resolve);  // 委派給 Common/System/Bootstrap
    }

    // 3. 【打破雙親委派】優先嘗試自行從 WebApp 載入
    try {
        clazz = findClass(name);  // 搜索 /WEB-INF/classes、/WEB-INF/lib
        if (clazz != null) {
            if (resolve) resolveClass(clazz);
            return clazz;
        }
    } catch (ClassNotFoundException ignored) {}

    // 4. 兜底委派父加載器(Common → System → Bootstrap)
    return super.loadClass(name, resolve);
}

// 判斷是否為容器核心類(不可被 WebApp 覆蓋)
private boolean isContainerClass(String name) {
    return name.startsWith("org.apache.catalina.") ||
           name.startsWith("org.apache.tomcat.") ||
           name.startsWith("org.apache.jasper.") ||
           name.startsWith("org.apache.naming.") ||
           name.startsWith("org.apache.el.") ;
}

// 判斷是否為 Java 核心類
private boolean isSystemClass(String name) {
    return name.startsWith("java.") ||
           name.startsWith("javax.") ||
           name.startsWith("sun.") ||
           name.startsWith("com.sun.") ||
           name.startsWith("jdk.") ;
}

三、關鍵設計決策解讀

設計點標準雙親委派Tomcat WebAppClassLoader理由
加載順序父優先 → 子子優先(WebApp 私有)→ 父保證應用使用自身 WEB-INF/lib 版本,實現版本隔離
核心類保護天然由 Bootstrap 加載額外維護 packageTriggers 白名單防止應用覆蓋 java.*、javax.*、org.apache.catalina.* 導致容器崩潰
熱部署支撐不支持銷毀舊 WebAppClassLoader,創建新實例類卸載需 GC 回收 ClassLoader,可能導致 Metaspace 洩漏
JSP 熱編譯—獨立 JSP ClassLoader,父=WebAppClassLoader單個 JSP 修改只需銷毀其 ClassLoader,不影響整個 WebApp

四、常見類衝突故障與排查

  1. LinkageError / ClassCastException:同一類被不同 ClassLoader 加載(如 log4j.jar 同時存在於 Common 與 WebApp)。解決:統一放 Common 或統一放 WebApp,或配置 loader.delegate=true 強制委派;
  2. NoClassDefFoundError:依賴類在父加載器不可見。解決:將共享庫上移至 Common,或在 META-INF/context.xml 配置 <Loader delegate="true"/>;
  3. Metaspace OOM:頻繁熱部署導致舊 WebAppClassLoader 未被 GC 回收(常見於 ThreadLocal、JDBC 驅動註冊、ShutdownHook 洩漏)。排查:jmap -histo:live 統計類加載器實例數,配合 -XX:+ClassUnloadingWithConcurrentMark。

五、最佳實踐建議

  • 應用私有庫(Spring、MyBatis、Jackson 等)務必放入 WEB-INF/lib,勿上放 Common;
  • 容器級共享資源(DataSource、JMS ConnectionFactory)通過 JNDI 暴露,而非共享 JAR;
  • 生產環境關閉 autoDeploy,採用藍綠部署/滾動升級替代熱部署;
  • 升級 Tomcat 版本時,檢查 packageTriggers 變更,避免核心類保護名單變動導致應用啟動異常。
「Tomcat 類加載器是『違背雙親委派的教科書級案例』——為了多應用隔離,它大膽打破規則,卻又在核心類保護上嚴守底線。讀懂這份源碼,就讀懂了 Java 容器隔離的本質。」
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved