1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Tomcat 第六篇:類加載機制

        共 11019字,需瀏覽 23分鐘

         ·

        2020-10-04 15:44

        1. 引言

        Tomcat 在部署 Web 應(yīng)用的時候,是將應(yīng)用放在 webapps 文件夾目錄下,而 webapps 對應(yīng)到 Tomcat 中是容器 Host ,里面的文件夾則是對應(yīng)到 Context ,在 Tomcat 啟動以后, webapps 中的所有的 Web 應(yīng)用都可以提供服務(wù)。

        這里會涉及到一個問題, webapps 下面不止會有一個應(yīng)用,比如有 APP1 和 APP2 兩個應(yīng)用,它們分別有自己獨立的依賴 jar 包,這些 jar 包會位于 APP 的 WEB-INFO/lib 這個目錄下,這些 jar 包大概率是會有重復(fù)的,比如常用的 Spring 全家桶,在這里面,版本肯定會有不同,那么 Tomcat 是如何處理的?

        2. JVM 類加載機制

        說到 Tomcat 的類加載機制,有一個繞不開的話題是 JVM 是如何進行類加載的,畢竟 Tomcat 也是運行在 JVM 上的。

        以下內(nèi)容參考自周志明老師的 「深入理解 Java 虛擬機」。

        2.1 什么是類的加載

        類的加載指的是將類的 .class 文件中的二進制數(shù)據(jù)讀入到內(nèi)存中,將其放在運行時數(shù)據(jù)區(qū)的方法區(qū)內(nèi),然后在堆區(qū)創(chuàng)建一個 java.lang.Class 對象,用來封裝類在方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)。類的加載的最終產(chǎn)品是位于堆區(qū)中的 Class 對象, Class 對象封裝了類在方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu),并且向 Java 程序員提供了訪問方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)的接口。

        類加載器并不需要等到某個類被 「首次主動使用」 時再加載它, JVM 規(guī)范允許類加載器在預(yù)料某個類將要被使用時就預(yù)先加載它,如果在預(yù)先加載的過程中遇到了 .class 文件缺失或存在錯誤,類加載器必須在程序首次主動使用該類時才報告錯誤( LinkageError 錯誤)如果這個類一直沒有被程序主動使用,那么類加載器就不會報告錯誤。

        加載.class文件的方式
        –?從本地系統(tǒng)中直接加載
        –?通過網(wǎng)絡(luò)下載.class文件
        –?從zip,jar等歸檔文件中加載.class文件
        –?從專有數(shù)據(jù)庫中提取.class文件
        –?將Java源文件動態(tài)編譯為.class文件

        2.2 類生命周期

        接下來,我們看下一個類的生命周期:

        一個類型從被加載到虛擬機內(nèi)存中開始,到卸載出內(nèi)存為止,它的整個生命周期將會經(jīng)歷加載(Loading)、驗證(Verification)、準(zhǔn)備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸載(Unloading)七個階段,其中驗證、準(zhǔn)備、解析三個部分統(tǒng)稱為連接(Linking)。

        2.3 雙親委派模型

        Java 提供三種類型的系統(tǒng)類加載器:

        • 啟動類加載器(Bootstrap ClassLoader):由 C++ 語言實現(xiàn),屬于 JVM 的一部分,其作用是加載 \lib 目錄中的文件,或者被 -Xbootclasspath 參數(shù)所指定的路徑中的文件,并且該類加載器只加載特定名稱的文件(如 rt.jar ),而不是該目錄下所有的文件。啟動類加載器無法被 Java 程序直接引用。
        • 擴展類加載器( Extension ClassLoader ):由 sun.misc.Launcher.ExtClassLoader 實現(xiàn),它負(fù)責(zé)加載 \lib\ext 目錄中的,或者被 java.ext.dirs 系統(tǒng)變量所指定的路徑中的所有類庫,開發(fā)者可以直接使用擴展類加載器。
        • 應(yīng)用程序類加載器( Application ClassLoader ):也稱系統(tǒng)類加載器,由 sun.misc.Launcher.AppClassLoader 實現(xiàn)。負(fù)責(zé)加載用戶類路徑( Class Path )上所指定的類庫,開發(fā)者可以直接使用這個類加載器,如果應(yīng)用程序中沒有自定義過自己的類加載器,一般情況下這個就是程序中默認(rèn)的類加載器。

        「雙親委派模型的工作機制:」

        如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每一個層次的類加載器都是如此,因此所有的加載請求最終都應(yīng)該傳送到頂層的啟動類加載器中,只有當(dāng)父加載器反饋自己無法完成這個加載請求(它的搜索范圍中沒有找到所需的類)時,子加載器才會嘗試自己去加載。

        「為什么?」

        例如類 java.lang.Object ,它存放在 rt.jar 之中。無論哪一個類加載器都要加載這個類。最終都是雙親委派模型最頂端的 Bootstrap 類加載器去加載。因此 Object 類在程序的各種類加載器環(huán)境中都是同一個類。相反,如果沒有使用雙親委派模型,由各個類加載器自行去加載的話,如果用戶編寫了一個稱為 「java.lang.Object」 的類,并存放在程序的 ClassPath 中,那系統(tǒng)中將會出現(xiàn)多個不同的 Object 類, java 類型體系中最基礎(chǔ)的行為也就無法保證,應(yīng)用程序也將會一片混亂。

        3. Tomcat 類加載機制

        先整體看下 Tomcat 類加載器:

        可以看到,在原來的 JVM 的類加載機制上面, Tomcat 新增了幾個類加載器,包括 3 個基礎(chǔ)類加載器和每個 Web 應(yīng)用的類加載器。

        3 個基礎(chǔ)類加載器在 conf/catalina.properties 中進行配置:

        common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"

        server.loader=

        shared.loader=
        • Common: 以應(yīng)用類加載器為父類,是 Tomcat 頂層的公用類加載器,其路徑由 conf/catalina.properties 中的 common.loader 指定,默認(rèn)指向 ${catalina.home}/lib 下的包。
        • Catalina: 以 Common 類加載器為父類,是用于加載 Tomcat 應(yīng)用服務(wù)器的類加載器,其路徑由 server.loader 指定,默認(rèn)為空,此時 Tomcat 使用 Common 類加載器加載應(yīng)用服務(wù)器。
        • Shared: 以 Common 類加載器為父類,是所有 Web 應(yīng)用的父類加載器,其路徑由 shared.loader 指定,默認(rèn)為空,此時 Tomcat 使用 Common 類加載器作為 Web 應(yīng)用的父加載器。
        • Web 應(yīng)用: 以 Shared 類加載器為父類,加載 /WEB-INF/classes 目錄下的未壓縮的 Class 和資源文件以及 /WEB-INF/lib 目錄下的 jar 包,該類加載器只對當(dāng)前 Web 應(yīng)用可見,對其他 Web 應(yīng)用均不可見。

        4. Tomcat 類加載機制源碼

        4.1 ClassLoader 的創(chuàng)建

        先看下加載器類圖:

        先從 BootStrap 的 main 方法看起:

        public?static?void?main(String?args[])?{
        ????synchronized?(daemonLock)?{
        ????????if?(daemon?==?null)?{
        ????????????//?Don't?set?daemon?until?init()?has?completed
        ????????????Bootstrap?bootstrap?=?new?Bootstrap();
        ????????????try?{
        ????????????????bootstrap.init();
        ????????????}?catch?(Throwable?t)?{
        ????????????????handleThrowable(t);
        ????????????????t.printStackTrace();
        ????????????????return;
        ????????????}
        ????????????daemon?=?bootstrap;
        ????????}?else?{
        ????????????//?When?running?as?a?service?the?call?to?stop?will?be?on?a?new
        ????????????//?thread?so?make?sure?the?correct?class?loader?is?used?to
        ????????????//?prevent?a?range?of?class?not?found?exceptions.
        ????????????Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
        ????????}
        ????????//?省略其余代碼...
        ????}
        }

        可以看到這里先判斷了 bootstrap 是否為 null ,如果不為 null 直接把 Catalina ClassLoader 設(shè)置到了當(dāng)前線程,如果為 null 下面是走到了 init() 方法。

        public?void?init()?throws?Exception?{
        ????//?初始化類加載器
        ????initClassLoaders();
        ????//?設(shè)置線程類加載器,將容器的加載器傳入
        ????Thread.currentThread().setContextClassLoader(catalinaLoader);
        ????//?設(shè)置區(qū)安全類加載器
        ????SecurityClassLoad.securityClassLoad(catalinaLoader);
        ????//?省略其余代碼...
        }

        接著這里看到了會調(diào)用 initClassLoaders() 方法進行類加載器的初始化,初始化完成后,同樣會設(shè)置 Catalina ClassLoader 到當(dāng)前線程。

        private?void?initClassLoaders()?{
        ????try?{
        ????????commonLoader?=?createClassLoader("common",?null);
        ????????if?(commonLoader?==?null)?{
        ????????????//?no?config?file,?default?to?this?loader?-?we?might?be?in?a?'single'?env.
        ????????????commonLoader?=?this.getClass().getClassLoader();
        ????????}
        ????????catalinaLoader?=?createClassLoader("server",?commonLoader);
        ????????sharedLoader?=?createClassLoader("shared",?commonLoader);
        ????}?catch?(Throwable?t)?{
        ????????handleThrowable(t);
        ????????log.error("Class?loader?creation?threw?exception",?t);
        ????????System.exit(1);
        ????}
        }

        看到這里應(yīng)該就清楚了,會創(chuàng)建三個 ClassLoader :CommClassLoader , Catalina ClassLoader , SharedClassLoader ,正好對應(yīng)前面介紹的三個基礎(chǔ)類加載器。

        接著進入 createClassLoader() 查看代碼:

        private?ClassLoader?createClassLoader(String?name,?ClassLoader?parent)
        ????throws?Exception?
        {

        ????String?value?=?CatalinaProperties.getProperty(name?+?".loader");
        ????if?((value?==?null)?||?(value.equals("")))
        ????????return?parent;

        ????value?=?replace(value);

        ????List?repositories?=?new?ArrayList<>();

        ????String[]?repositoryPaths?=?getPaths(value);

        ????for?(String?repository?:?repositoryPaths)?{
        ????????//?Check?for?a?JAR?URL?repository
        ????????try?{
        ????????????@SuppressWarnings("unused")
        ????????????URL?url?=?new?URL(repository);
        ????????????repositories.add(new?Repository(repository,?RepositoryType.URL));
        ????????????continue;
        ????????}?catch?(MalformedURLException?e)?{
        ????????????//?Ignore
        ????????}

        ????????//?Local?repository
        ????????if?(repository.endsWith("*.jar"))?{
        ????????????repository?=?repository.substring
        ????????????????(0,?repository.length()?-?"*.jar".length());
        ????????????repositories.add(new?Repository(repository,?RepositoryType.GLOB));
        ????????}?else?if?(repository.endsWith(".jar"))?{
        ????????????repositories.add(new?Repository(repository,?RepositoryType.JAR));
        ????????}?else?{
        ????????????repositories.add(new?Repository(repository,?RepositoryType.DIR));
        ????????}
        ????}

        ????return?ClassLoaderFactory.createClassLoader(repositories,?parent);
        }

        可以看到,這里加載的資源正好是我們剛才看到的配置文件 conf/catalina.properties 中的 common.loaderserver.loadershared.loader 。

        4.2 ClassLoader 加載過程

        直接打開 ParallelWebappClassLoader ,至于為啥不是看 WebappClassLoader ,從名字上就知道 ParallelWebappClassLoader 是一個并行的 WebappClassLoader 。

        然后看下 ParallelWebappClassLoader 的 loadclass 方法是在它的父類 WebappClassLoaderBase 中實現(xiàn)的。

        4.2.1 第一步:

        public?Class?loadClass(String?name,?boolean?resolve)?throws?ClassNotFoundException?{

        ????synchronized?(getClassLoadingLock(name))?{
        ????????if?(log.isDebugEnabled())
        ????????????log.debug("loadClass("?+?name?+?",?"?+?resolve?+?")");
        ????????Class?clazz?=?null;

        ????????//?Log?access?to?stopped?class?loader
        ????????checkStateForClassLoading(name);

        ????????//?(0)?Check?our?previously?loaded?local?class?cache
        ????????clazz?=?findLoadedClass0(name);?
        ????????if?(clazz?!=?null)?{
        ????????????if?(log.isDebugEnabled())
        ????????????????log.debug("??Returning?class?from?cache");
        ????????????if?(resolve)
        ????????????????resolveClass(clazz);
        ????????????return?clazz;
        ????????}
        ????????//?省略其余...

        首先調(diào)用 findLoaderClass0() 方法檢查 WebappClassLoader 中是否加載過此類。

        protected?Class?findLoadedClass0(String?name)?{

        ????String?path?=?binaryNameToPath(name,?true);

        ????ResourceEntry?entry?=?resourceEntries.get(path);
        ????if?(entry?!=?null)?{
        ????????return?entry.loadedClass;
        ????}
        ????return?null;
        }

        WebappClassLoader 加載過的類都存放在 resourceEntries 緩存中。

        protected?final?Map?resourceEntries?=?new?ConcurrentHashMap<>();

        4.2.2 第二步:

        ????//?省略其余...
        ????clazz?=?findLoadedClass(name);
        ????if?(clazz?!=?null)?{
        ????????if?(log.isDebugEnabled())
        ????????????log.debug("??Returning?class?from?cache");
        ????????if?(resolve)
        ????????????resolveClass(clazz);
        ????????return?clazz;
        ????}
        ????//?省略其余...

        如果第一步?jīng)]有找到,則繼續(xù)檢查 JVM 虛擬機中是否加載過該類。調(diào)用 ClassLoader 的 findLoadedClass() 方法檢查。

        4.2.3 第三步:

        ????ClassLoader?javaseLoader?=?getJavaseClassLoader();
        ????boolean?tryLoadingFromJavaseLoader;
        ????try?{

        ????????URL?url;
        ????????if?(securityManager?!=?null)?{
        ????????????PrivilegedAction?dp?=?new?PrivilegedJavaseGetResource(resourceName);
        ????????????url?=?AccessController.doPrivileged(dp);
        ????????}?else?{
        ????????????url?=?javaseLoader.getResource(resourceName);
        ????????}
        ????????tryLoadingFromJavaseLoader?=?(url?!=?null);
        ????}?catch?(Throwable?t)?{

        ????????ExceptionUtils.handleThrowable(t);

        ????????tryLoadingFromJavaseLoader?=?true;
        ????}

        ????if?(tryLoadingFromJavaseLoader)?{
        ????????try?{
        ????????????clazz?=?javaseLoader.loadClass(name);
        ????????????if?(clazz?!=?null)?{
        ????????????????if?(resolve)
        ????????????????????resolveClass(clazz);
        ????????????????return?clazz;
        ????????????}
        ????????}?catch?(ClassNotFoundException?e)?{
        ????????????//?Ignore
        ????????}
        ????}

        如果前兩步都沒有找到,則使用系統(tǒng)類加載該類(也就是當(dāng)前 JVM 的 ClassPath )。為了防止覆蓋基礎(chǔ)類實現(xiàn),這里會判斷 class 是不是 JVMSE 中的基礎(chǔ)類庫中類。

        4.2.4 第四步:

        ????boolean?delegateLoad?=?delegate?||?filter(name,?true);

        ????//?(1)?Delegate?to?our?parent?if?requested
        ????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
        ????????}
        ????}

        先判斷是否設(shè)置了 delegate 屬性,設(shè)置為 true ,那么就會完全按照 JVM 的"雙親委托"機制流程加載類。

        若是默認(rèn)的話,是先使用 WebappClassLoader 自己處理加載類的。當(dāng)然,若是委托了,使用雙親委托亦沒有加載到 class 實例,那還是最后使用 WebappClassLoader 加載。

        4.2.5 ?第五步:

        ????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
        ????}

        若是沒有委托,則默認(rèn)會首次使用 WebappClassLoader 來加載類。通過自定義 findClass() 定義處理類加載規(guī)則。

        findClass() 會去 Web-INF/classes 目錄下查找類。

        4.2.6 ?第六步:

        ????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
        ????????}
        ????}

        若是 WebappClassLoader 在 /WEB-INF/classes 、 /WEB-INF/lib 下還是查找不到 class ,那么無條件強制委托給 System 、 Common 類加載器去查找該類。

        4.2.7 小結(jié)

        Web 應(yīng)用類加載器默認(rèn)的加載順序是:

        1. 先從緩存中加載;
        2. 如果沒有,則從 JVM 的 Bootstrap 類加載器加載;
        3. 如果沒有,則從當(dāng)前類加載器加載(按照 WEB-INF/classes 、 WEB-INF/lib 的順序);
        4. 如果沒有,則從父類加載器加載,由于父類加載器采用默認(rèn)的委派模式,所以加載順序是 AppClassLoader 、 Common 、 Shared 。

        參考

        https://www.jianshu.com/p/69c4526b843d





        感謝閱讀



        瀏覽 42
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            国产精品夫妻 | 免费看成人747474九号视频在线观看 | 成人首发 | 精品999久久久一级毛片 | 操丝袜人妻 | 我被继夫添我阳道舒服口述 | 91国偷自产中文字幕婷婷 | 一二三级全中文 | 永久免费的黄冈建网站 | 日本不卡a|