Tomcat-類加載

概述

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

一、JVM類加載器

JVM類加載器.png
  • 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類加載器

Tomcat類加載器.png
  • WebAppClassLoader Tomcat為給每個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-------

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容