死磕Tomcat系列(4)——Tomcat中的類加載器
在學(xué)習(xí)Tomcat中的類加載器,并且Tomcat為什么要實(shí)現(xiàn)自己的類加載器打破雙親委派模型原因之前,我們首先需要知道Java中定義的類加載器是什么,雙親委派模型是什么。
Java中的類加載器
類加載器負(fù)責(zé)在程序運(yùn)行時(shí)將java文件動(dòng)態(tài)加載到JVM中
從Java虛擬機(jī)的角度來(lái)講的話,存在兩種不同的類加載器:
啟動(dòng)類加載器(Bootstrap ClassLoader):這個(gè)類加載器是使用C++語(yǔ)言實(shí)現(xiàn)的,是虛擬機(jī)自身的一部分。
-
其他的類加載器:這些類加載器都由Java語(yǔ)言實(shí)現(xiàn),獨(dú)立于虛擬機(jī)外部,并且全都繼承自抽象類
java.lang.ClassLoader,其中其他類加載器大概又分為- ExtensionClassLoader:這個(gè)類加載器由
ExtClassLoader實(shí)現(xiàn),它負(fù)責(zé)加載JAVA_HOME/lib/ext目錄中的所有類,或者被java.ext.dir系統(tǒng)變量所指定的路徑中所有的類。 - ApplicationClassLoader:這個(gè)類加載器是由
AppClassLoader實(shí)現(xiàn)的,它負(fù)責(zé)加載用戶類路徑(ClassPath)上所指定的所有類,如果應(yīng)用中沒有自定義自己的類加載器,那么一般情況就是程序中默認(rèn)的類加載器。 - 自定義加載器:根據(jù)自己需求,自定義加載特定路徑的加載器。
- ExtensionClassLoader:這個(gè)類加載器由

對(duì)于任意一個(gè)類,都需要由加載它的類加載器和這個(gè)類本身一同確立其在Java虛擬機(jī)中的唯一性
雙親委派模型
上圖中展示的層次結(jié)構(gòu),稱之為類加載器的雙親委派模型。雙親委派模型要求除了頂層的啟動(dòng)類加載器外,其他加載器都應(yīng)該有自己的父加載器。這里的父子關(guān)系不是通過(guò)繼承來(lái)實(shí)現(xiàn)的,而是通過(guò)設(shè)置parent變量來(lái)實(shí)現(xiàn)的。
雙親委派模型工作過(guò)程是:如果收到一個(gè)類加載的請(qǐng)求,本身不會(huì)先加載此類,而是會(huì)先將此請(qǐng)求委派給父類加載器去完成,每個(gè)層次都是如此,直到啟動(dòng)類加載器中,只有父類都沒有加載此文件,那么子類才會(huì)嘗試自己去加載。
為什么要設(shè)置雙親委派模型呢?其實(shí)是為了保證Java程序的穩(wěn)定運(yùn)行,例如Object類,它是存放在rt.jar中,無(wú)論哪一個(gè)類加載器要加載Object類,最終都會(huì)委托給頂層的BootStrapClassLoader,所以所有的類中使用的Object都是同一個(gè)類,相反如果沒有雙親委派模型的話,那么隨意一個(gè)類加載器都可以定義一個(gè)新的Object類,那么應(yīng)用程序?qū)?huì)變得非?;靵y。其實(shí)雙親委派模型代碼非常簡(jiǎn)單。實(shí)現(xiàn)在ClassLoader中的loadClass方法下。
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先,檢查請(qǐng)求類是否被加載過(guò)
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
// 如果沒被本類類加載器加載過(guò),先委托給父類進(jìn)行加載
if (parent != null) {
c = parent.loadClass(name, false);
} else {
// 如果沒有父類,則表明在頂層,就交給BootStrap類加載器加載
c = findBootstrapClassOrNull(name);
}
// 如果最頂層的類也找不到,那么就會(huì)拋出ClassNotFoundException異常
} catch (ClassNotFoundException e) {
}
// 如果父類都沒有加載過(guò)此類,子類才開始加載此類
if (c == null) {
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
我們可以看到findClass方法是需要子類自己去實(shí)現(xiàn)的邏輯。
Tomcat中的類加載器
下面的簡(jiǎn)圖是Tomcat9版本的官方文檔給出的Tomcat的類加載器的圖。
Bootstrap
|
System
|
Common
/ \
Webapp1 Webapp2 ..
- Bootstrap : 是Java的最高的加載器,用C語(yǔ)言實(shí)現(xiàn),主要用來(lái)加載JVM啟動(dòng)時(shí)所需要的核心類,例如
$JAVA_HOME/jre/lib/ext路徑下的類。 - System: 會(huì)加載
CLASSPATH系統(tǒng)變量所定義路徑的所有的類。 - Common:會(huì)加載Tomcat路徑下的lib文件下的所有類。
- Webapp1、Webapp2……: 會(huì)加載webapp路徑下項(xiàng)目中的所有的類。一個(gè)項(xiàng)目對(duì)應(yīng)一個(gè)WebappClassLoader,這樣就實(shí)現(xiàn)了應(yīng)用之間類的隔離了。
這3個(gè)部分,在上面的Java雙親委派模型圖中都有體現(xiàn)。不過(guò)可以看到ExtClassLoader沒有畫出來(lái),可以理解為是跟bootstrap合并了,都是去JAVA_HOME/jre/lib下面加載類。 那么Tomcat為什么要自定義類加載器呢?
- 隔離不同應(yīng)用:部署在同一個(gè)Tomcat中的不同應(yīng)用A和B,例如A用了Spring2.5。B用了Spring3.5,那么這兩個(gè)應(yīng)用如果使用的是同一個(gè)類加載器,那么Web應(yīng)用就會(huì)因?yàn)閖ar包覆蓋而無(wú)法啟動(dòng)。
- 靈活性:Web應(yīng)用之間的類加載器相互獨(dú)立,那么就可以根據(jù)修改不同的文件重建不同的類加載器替換原來(lái)的。從而不影響其他應(yīng)用。
- 性能:如果在一個(gè)Tomcat部署多個(gè)應(yīng)用,多個(gè)應(yīng)用中都有相同的類庫(kù)依賴。那么可以把這相同的類庫(kù)讓Common類加載器進(jìn)行加載。
Tomcat自定義了WebAppClassLoader類加載器。打破了雙親委派的機(jī)制,即如果收到類加載的請(qǐng)求,會(huì)嘗試自己去加載,如果找不到再交給父加載器去加載,目的就是為了優(yōu)先加載Web應(yīng)用自己定義的類。我們知道ClassLoader默認(rèn)的loadClass方法是以雙親委派的模型進(jìn)行加載類的,那么Tomcat既然要打破這個(gè)規(guī)則,就要重寫loadClass方法,我們可以看WebAppClassLoader類中重寫的loadClass方法。
@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
Class<?> clazz = null;
// 1. 從本地緩存中查找是否加載過(guò)此類
clazz = findLoadedClass0(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return clazz;
}
// 2. 從AppClassLoader中查找是否加載過(guò)此類
clazz = findLoadedClass(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return clazz;
}
String resourceName = binaryNameToPath(name, false);
// 3. 嘗試用ExtClassLoader 類加載器加載類,防止Web應(yīng)用覆蓋JRE的核心類
ClassLoader javaseLoader = getJavaseClassLoader();
boolean tryLoadingFromJavaseLoader;
try {
URL url;
if (securityManager != null) {
PrivilegedAction<URL> dp = new PrivilegedJavaseGetResource(resourceName);
url = AccessController.doPrivileged(dp);
} else {
url = javaseLoader.getResource(resourceName);
}
tryLoadingFromJavaseLoader = (url != null);
} catch (Throwable t) {
tryLoadingFromJavaseLoader = true;
}
boolean delegateLoad = delegate || filter(name, true);
// 4. 判斷是否設(shè)置了delegate屬性,如果設(shè)置為true那么就按照雙親委派機(jī)制加載類
if (delegateLoad) {
if (log.isDebugEnabled())
log.debug(" Delegating to parent classloader1 " + parent);
try {
clazz = Class.forName(name, false, parent);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Loading class from parent");
if (resolve)
resolveClass(clazz);
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
// 5. 默認(rèn)是設(shè)置delegate是false的,那么就會(huì)先用WebAppClassLoader進(jìn)行加載
if (log.isDebugEnabled())
log.debug(" Searching local repositories");
try {
clazz = findClass(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Loading class from local repository");
if (resolve)
resolveClass(clazz);
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
// 6. 如果此時(shí)在WebAppClassLoader沒找到類,那么就委托給AppClassLoader去加載
if (!delegateLoad) {
if (log.isDebugEnabled())
log.debug(" Delegating to parent classloader at end: " + parent);
try {
clazz = Class.forName(name, false, parent);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Loading class from parent");
if (resolve)
resolveClass(clazz);
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
}
throw new ClassNotFoundException(name);
}
最后借用Tomcat官網(wǎng)上的話總結(jié):
Web應(yīng)用默認(rèn)的類加載順序是(打破了雙親委派規(guī)則):
- 先從JVM的BootStrapClassLoader中加載。
- 加載Web應(yīng)用下
/WEB-INF/classes中的類。 - 加載Web應(yīng)用下
/WEB-INF/lib/*.jap中的jar包中的類。 - 加載上面定義的System路徑下面的類。
- 加載上面定義的Common路徑下面的類。
如果在配置文件中配置了<Loader delegate="true"/>,那么就是遵循雙親委派規(guī)則,加載順序如下:
- 先從JVM的BootStrapClassLoader中加載。
- 加載上面定義的System路徑下面的類。
- 加載上面定義的Common路徑下面的類。
- 加載Web應(yīng)用下
/WEB-INF/classes中的類。 - 加載Web應(yīng)用下
/WEB-INF/lib/*.jap中的jar包中的類。