概述
Java的類加載,就是把字節(jié)碼格式“.class”文件加載到JVM的方法區(qū),并在JVM的堆區(qū)建立一個java.lang.Class對象的實例,用來封裝Java類相關(guān)的數(shù)據(jù)和方法。都知道Tomcat打破了Java的雙親委托機(jī)制,本節(jié)就來對比下JVM類加載器和Tomcat類加載器;
一、JVM類加載器

-
BootstrapClassLoader是啟動類加載器,由C語言實現(xiàn),用來加載JVM啟動時所需要的核心類,比如rt.jar、resources.jar等。 -
ExtClassLoader是擴(kuò)展類加載器,用來加載\jre\lib\ext目錄下JAR包。 -
AppClassLoader是系統(tǒng)類加載器,用來加載classpath下的類,應(yīng)用程序默認(rèn)用它來加載類。 -
CustomClassLoader自定義類加載器,用來加載自定義路徑下的類。
這些類加載器的工作原理是一樣的,區(qū)別是它們的加載路徑不同,也就是說findClass查找的路徑不同。雙親委托機(jī)制是為了保證一個Java類在JVM中是唯一的,假如你不小心寫了一個與JRE核心類同名的類,比如Object類,雙親委托機(jī)制能保證加載的是JRE里的那個Object類,而不是你寫的Object類。這是因為AppClassLoader在加載你的Object類時,會委托給ExtClassLoader去加載,而ExtClassLoader又會委托給BootstrapClassLoader,BootstrapClassLoader發(fā)現(xiàn)自己已經(jīng)加載過了Object類,會直接返回,不會去加載你寫的Object類。
public abstract class ClassLoader {
//每個類加載器都有個?加載器
private final ClassLoader parent;
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 查找下這個類是不是已經(jīng)加載過了
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
// 父加載器不為空,先委托給父加載器去加載,注意這是個遞歸調(diào)用
c = parent.loadClass(name, false);
} else {
// 如果父加載器為空,查找Bootstrap加載器是不是加載過了
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
if (c == null) {
// 如果父加載器沒加載成功,調(diào)用自己的findClass去加載
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
protected Class<?> findClass(String name){
//1. 根據(jù)傳入的類名name,到在特定目錄下去尋找類文件,把.class?件讀入內(nèi)存
...
//2. 調(diào)?defineClass將字節(jié)數(shù)組轉(zhuǎn)成Class對象
return defineClass(buf, off, len);
}
// 將字節(jié)碼數(shù)組解析成Class對象,native法實現(xiàn)
protected final Class<?> defineClass(String name, byte[] b, int off, int len,
ProtectionDomain protectionDomain)
throws ClassFormatError
{
......
}
}
- JVM的類加載器是分層次的,它們有父子關(guān)系,每個類加載器都持有一個parent字段,指向父加載器。
- defineClass是個工具方法,它的職責(zé)是調(diào)用native方法把Java類的字節(jié)碼解析成一個Class對象,所謂的native方法就是由C語言實現(xiàn)的方法,Java通過JNI機(jī)制調(diào)用。
- findClass方法的主要職責(zé)就是找到“.class”文件,可能來自文件系統(tǒng)或者網(wǎng)絡(luò),找到后把“.class”文件讀到內(nèi)存得到字節(jié)碼數(shù)組,然后調(diào)用defineClass方法得到Class對象。
- loadClass是個public方法,說明它才是對外提供服務(wù)的接口,具體實現(xiàn)也比較清晰:首先檢查這個類是不是已經(jīng)被加載過了,如果加載過了直接返回,否則交給父加載器去加載。請你注意,這是一個遞歸調(diào)用,也就是說子加載器持有父加載器的引用,當(dāng)一個類加載器需要加載一個Java類時,會先委托父加載器去加載,然后父加載器在自己的加載路徑中搜索Java類,當(dāng)父加載器在自己的加載范圍內(nèi)找不到時,才會交還給子加載器加載,這就是雙親委托機(jī)制。
二、Tomcat類加載器

-
WebAppClassLoaderTomcat為給每個Web應(yīng)用創(chuàng)建一個WebAppClassLoader類加載器。一個Context容器組件對應(yīng)一個Web應(yīng)用,因此,每個Context容器負(fù)責(zé)創(chuàng)建和維護(hù)一個WebAppClassLoader加載器實例。這背后的原理是,不同的加載器實例加載的類被認(rèn)為是不同的類,即使它們的類名相同。這就相當(dāng)于在Java虛擬機(jī)內(nèi)部創(chuàng)建了一個個相互隔離的Java類空間,每一個Web應(yīng)用都有自己的類空間,Web應(yīng)用之間通過各自的類加載器互相隔離。
這個類加載器在StandardContext.startInternal()中被構(gòu)造:
if (getLoader() == null) {
WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
webappLoader.setDelegate(getDelegate());
setLoader(webappLoader);
}
-
SharedClassLoader作為WebAppClassLoader的父加載器,專門來加載Web應(yīng)用之間共享的類。如果WebAppClassLoader自己沒有加載到某個類,就會委托父加載器SharedClassLoader去加載這個類,SharedClassLoader會在指定目錄下加載共享類,之后返回給WebAppClassLoader,這樣共享的問題就解決了。 -
CatalinaClassloader專門來加載Tomcat自身的類。這樣設(shè)計有個問題,那Tomcat和各Web應(yīng)用之間需要共享一些類時該怎么辦呢? -
CommonClassLoader老辦法,還是再增加一個CommonClassLoader,作為CatalinaClassloader和SharedClassLoader的父加載器。CommonClassLoader能加載的類都可以被CatalinaClassLoader和SharedClassLoader 使用,而CatalinaClassLoader和SharedClassLoader能加載的類則與對方相互隔離。WebAppClassLoader可以使用SharedClassLoader加載到的類,但各個WebAppClassLoader實例之間相互隔離。
shared.loader和server.loader在catalina.properties中配置默認(rèn)為空,而且通常我們一個Tomcat容器只部署一個應(yīng)用,因此SharedClassLoader和CatalinaClassloader不會使用到;
關(guān)于Tomcat類加載器層次的維護(hù)代碼在Bootstrap.initClassLoaders中,這里不做分析,下面分析下WebAppClassLoader是如何打破雙親委派的
public synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
Class<?> clazz = null;
// 1. 先在本地cache查找該類是否已經(jīng)加載過
clazz = findLoadedClass0(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return (clazz);
}
// 2. 在本地緩存沒有的情況下,調(diào)用ClassLoader的findLoadedClass方法查看jvm是否已經(jīng)加載過此類,如果已經(jīng)加載則直接返回
clazz = findLoadedClass(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return (clazz);
}
// 3. 通過系統(tǒng)的來加載器加載此類
ClassLoader javaseLoader = getJavaseClassLoader();
try {
clazz = javaseLoader.loadClass(name);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
//4. 判斷是否需要委托給父類加載器進(jìn)行加載
boolean delegateLoad = delegate || filter(name);
// 5. false不執(zhí)行
if (delegateLoad) {
if (log.isDebugEnabled())
log.debug(" Delegating to parent classloader1 " + parent);
ClassLoader loader = parent;
if (loader == null)
loader = system;
try {
clazz = Class.forName(name, false, loader);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Loading class from parent");
if (resolve)
resolveClass(clazz);
return (clazz);
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
// 6. 在本地Web應(yīng)用目錄下查找并加載
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
}
// 7. 通過父類加載器去加載
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);
}
-
1.首先從當(dāng)前ClassLoader的本地緩存中加載類,如果找到則返回。 -
2.在本地緩存沒有的情況下,調(diào)用ClassLoader的findLoadedClass方法查看jvm是否已經(jīng)加載過此類,如果已經(jīng)加載則直接返回。 -
3.用ExtClassLoader去加載,這一步比較關(guān)鍵,目的防止Web應(yīng)用自己的類覆蓋JRE的核心類。因為Tomcat需要打破雙親委托機(jī)制,假如Web應(yīng)用里自定義了一個叫Object的類,如果先加載這個Object類,就會覆蓋JRE里面的那個Object類,這就是為什么Tomcat的類加載器會優(yōu)先嘗試用ExtClassLoader去加載,因為ExtClassLoader會委托給BootstrapClassLoader去加載,BootstrapClassLoader發(fā)現(xiàn)自己已經(jīng)加載了Object類,直接返回給Tomcat的類加載器,這樣Tomcat的類加載器就不會去加載Web應(yīng)用下的Object類了,也就避免了覆蓋JRE核心類的問題。 -
4.判斷是否需要委托給父類加載器進(jìn)行加載,delegate屬性默認(rèn)為false,那么delegatedLoad的值就取決于filter的返回值了,filter方法中根據(jù)包名來判斷是否需要進(jìn)行委托加載,默認(rèn)情況下會返回false。因此delegatedLoad為false。 -
5.delegatedLoad為false,那么此時不會委托父加載器去加載; -
6.如果ExtClassLoader加載器加載失敗,也就是說JRE核心類中沒有這類,那么就在本地Web應(yīng)用目錄下查找并加載。 -
7.如果還是沒有加載到類,并且不采用委托機(jī)制的話,則通過父類加載器去加載。
從上面的過程我們可以看到,Tomcat的類加載器打破了雙親委托機(jī)制,沒有一上來就直接委托給父加載器,而是先在本地目錄下加載,為了避免本地目錄下的類覆蓋JRE的核心類,先嘗試用JVM擴(kuò)展類加載器ExtClassLoader去加載。
-------over-------