Tomcat 作為 Servlet 容器,其類加載器體系打破了標準雙親委派模型,實現了 Web 應用間的類隔離、熱部署與庫版本共存。本章筆記梳理核心類加載器層級、委派邏輯與關鍵源碼流程。
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 類
▼
// 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 |
log4j.jar 同時存在於 Common 與 WebApp)。解決:統一放 Common 或統一放 WebApp,或配置 loader.delegate=true 強制委派;META-INF/context.xml 配置 <Loader delegate="true"/>;jmap -histo:live 統計類加載器實例數,配合 -XX:+ClassUnloadingWithConcurrentMark。packageTriggers 變更,避免核心類保護名單變動導致應用啟動異常。「Tomcat 類加載器是『違背雙親委派的教科書級案例』——為了多應用隔離,它大膽打破規則,卻又在核心類保護上嚴守底線。讀懂這份源碼,就讀懂了 Java 容器隔離的本質。」