Tomcat類加載與應(yīng)用間的類隔離

1.什么是類加載泳姐?

類的加載指的是將類的.class文件中的二進制數(shù)據(jù)讀入到內(nèi)存(JVM)中昭伸,將其放在運行時數(shù)據(jù)放入方法區(qū)內(nèi)(這里方法區(qū)也稱永久代泣洞,但是在Jdk1.8后取消這塊改名叫元空間),然后在堆內(nèi)(heap)創(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)的接口罩润。

2.類的生命周期

一個類的生命周期包括:加載,驗證翼馆,準備割以,解析金度,初始化的五個過程,這個五個過程严沥。其中加載的過程對于開發(fā)人員來說是可控制的猜极,至于原因就在加載的過程中包括三個階段:通過一個類的全限定類名來獲取該類的二進制字節(jié)流,將二進制字節(jié)流所表示的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)到方法區(qū)所運行時的數(shù)據(jù)結(jié)構(gòu)消玄,最后在堆中生成代表該類的java.lang.Class的對象跟伏,作為方法區(qū)數(shù)據(jù)訪問的入口,而由于JVM并沒有規(guī)定而我們?nèi)绻@取該類的二進制字節(jié)流翩瓜,所以我們可以使用默認的類加載和自定義的類加載受扳。

3.類加載的層次圖

在上面我們提到了開發(fā)人員可以使用默認的類加載或是自定義類加載器,這樣我們就會想到如果保證這些類加載不會產(chǎn)生沖突呢兔跌?首先我們需要先了解一下JDK默認的幾種類加載器:

(1) Bootstrap ClassLoader:

啟動類加載器(Bootstrap ClassLoader):由C++語言實現(xiàn)(針對HotSpot),負責(zé)將存放在JAVA_HOME下jre\lib目錄或-Xbootclasspath參數(shù)指定的路徑中的虛擬機識別的類庫加載到內(nèi)存中(譬如 rt.jar)勘高。

(2) Extension ClassLoader :

負責(zé)加載jre\lib\ext目錄或java.ext.dirs系統(tǒng)變量指定的路徑中的所有類庫。


(3) Application ClassLoader :

是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)實現(xiàn)的坟桅。它負責(zé)將系統(tǒng)類路徑(CLASSPATH)中指定的類庫加載到內(nèi)存中华望。開發(fā)者可以直接使用系統(tǒng)類加載器。由于這個類加載器是ClassLoader中的getSystemClassLoader()方法的返回值仅乓,因此一般稱為SystemClassLoader

以上就是jdk默認的三種類加載器赖舟,下圖我們可以看到JDK的類加載層次,圖中的箭頭就是一種雙親委派的加載模式夸楣。什么是雙親委派模型呢宾抓?

雙親委派模型:某一個特定的類加載器接受一個類加載的請求時候,首先先把這個類的請求委托給上級的類加載器裕偿,即父類的類加載器完成洞慎,而不是自己優(yōu)先嘗試加載該類,只有當(dāng)父類無法加載該類的時候嘿棘,自己再嘗試加載該類劲腿,如果自己也無法加載即拋出:ClassNotFoundException 和 NoClassDefFoundError。

至于為什么要使用這種類加載模型呢鸟妙?舉一個網(wǎng)上都用的例子:類java.lang.Object焦人,它存在在rt.jar中,無論哪一個類加載器要加載這個類重父,最終都是委派給處于模型最頂端的Bootstrap ClassLoader進行加載花椭,因此Object類在程序的各種類加載器環(huán)境中都是同一個類。相反房午,如果沒有雙親委派模型而是由各個類加載器自行加載的話矿辽,即便是同一個.class文件,由不同的類加載器加載即不是同一個類,如果用戶編寫了一個java.lang.Object的同名類并放在ClassPath中袋倔,那系統(tǒng)中將會出現(xiàn)多個不同的Object類雕蔽,程序?qū)⒒靵y。因此宾娜,如果開發(fā)者嘗試編寫一個與rt.jar類庫中重名的Java類批狐,可以正常編譯,但是永遠無法被加載運行前塔。

我們可以看到j(luò)dk里面的classloader方法的實現(xiàn)嚣艇,首先會嘗試看本地有沒有加載過該類,如果加載過华弓,即直接返回食零,否則獲取該類加載器的父類,如果父類不等于空该抒,則優(yōu)先委托給父類來加載慌洪,如果父類為空,則直接交給頂級的類加載器完成加載凑保,如果頂級類加載也無法加載冈爹,則才調(diào)用自己的類加載來findClass來加載該類。

4.Tomcat類加載

現(xiàn)在終于走到正題欧引,想必一定是tomcat并沒有完全遵循雙親委派的累加機制频伤,否則不會單獨拿出來講。首先我們先思考幾個問題:

1.如果在一個Tomcat內(nèi)部署多個應(yīng)用芝此,甚至多個應(yīng)用內(nèi)使用了某個類似的幾個不同版本憋肖,但它們之間卻互不影響。這是如何做到的婚苹。

2.如果多個應(yīng)用都用到了某類似的相同版本岸更,是否可以統(tǒng)一提供,不在各個應(yīng)用內(nèi)分別提供膊升,占用內(nèi)存呢怎炊。

至于第一個問題其實上面的講解已經(jīng)解答了,就是因為tomcat部署了多個應(yīng)用廓译,而多個應(yīng)用都采用自定義的類加載器评肆,所以即便是同一個類使用不同的類加載機制最終也是不一樣的類。

至于第二問題非区,我首先看看Tomcat的類加載層次:


我們看到webappClassLoader上面有一個common的類加載器瓜挽,它是所有webappClassLoader的父加載器,多個應(yīng)用匯存在公有的類庫征绸,而公有的類庫都會使用commonclassloader來實現(xiàn)久橙。這樣也就回答了第二個問題俄占;

由此我們也引出了如果不是公有的類呢,這些類就會使用webappClassLoader加載淆衷,而webappClassLoader的實現(xiàn)并沒有走雙親委派的模式颠放,這有是為何呢?

原因有兩個:

1)加載本類的classloader未知時吭敢,為了隔離不同的調(diào)用者,即類的隔離暮芭,采用了上下文類加載的模式加載類鹿驼;

2)當(dāng)前高層的接口在低層去實現(xiàn),而高層的類有需要低層的類加載的時候辕宏,這個時候畜晰,需要使用上下文類加載器去實現(xiàn)(后面會通過JDBC的加載來講解)

JDCB的類加載(經(jīng)典的線程上下文加載器)

private static Connection getConnection(

String url,java.util.Properties info,Class caller)throwsSQLException {

//由于DriverManger.class是由于jdk里的rt.jar包里面加載的,而實際調(diào)用的是com.mysql.jdbc.Driver的driver該類瑞筐,而是調(diào)用getConnection的方法時候凄鼻,下圖中的這個方法的到DriverManger這個類是頂級類加載器加載的,這個時候又要啟動該類的子類聚假,所以雙親委派是無法加載該類的块蚌,即圖二中,caller.getClassLoader是null膘格,這個時候就會調(diào)用 if 里面的線程上下文的加載器峭范,通過上下文加載的方式完成加載,最好驗證該是否可用瘪贱,完成獲取JDBC的連接纱控。


有點跑題,現(xiàn)在回到Tomcat類加載中菜秦,我們需要了解到底是采用了雙親委派還是上線文加載模式甜害。首先我們需要明確的一點就是基礎(chǔ)類肯,common類球昨,還是有servlet-api一定用雙親委派模式尔店,因為這些都是公有的類庫,且對于Servlet-api是不允許被重寫褪尝,也就是說如果你用自己的類加載的話闹获,會影響到應(yīng)用內(nèi)部得到正常運行了,也就是說只有加載app應(yīng)用的類時候才會引用上下文加載河哑。下面我們看看上線文加載的類:webappLoader避诽;

在Tomcat啟動時,會創(chuàng)建一系列的類加載器璃谨,在其主類Bootstrap的初始化過程中沙庐,會先初始化classloader鲤妥,然后將其綁定到Thread中。


其中initClassLoaders方法拱雏,會根據(jù)catalina.properties的配置棉安,創(chuàng)建相應(yīng)的classloader。由于默認只配置了common.loader屬性铸抑,所以其中只會創(chuàng)建一個出來commonClassLoader贡耽,然后,當(dāng)一個應(yīng)用啟動的時候鹊汛,會為其創(chuàng)建對應(yīng)的WebappClassLoader蒲赂。此時會將commonClassLoader設(shè)置為其parent。下面的代碼是StandardContext類在啟動時創(chuàng)建WebappLoader的代碼


這里的getParentClassLoader會從當(dāng)前組件的classLoader一直向上刁憋,找parent classLoader設(shè)置滥嘴。之后注意下一行代碼

webappLoader.setDelegate

這就是在設(shè)置后面Web應(yīng)用的類查找時是父優(yōu)先還是子優(yōu)先。這個配置可以在server.xml里至耻,對Context組件進行配置若皱。

即在Context元素下可以嵌套一個Loader元素,配置Loader的delegate即可尘颓,其默認為false走触,即子優(yōu)先。類似于這樣

delegate="true"/>

注意Loader還有一個屬性是reloadable疤苹,用于表明對于/WEB-INF/classes/ 和 /WEB-INF/lib 下資源發(fā)生變化時饺汹,是否重新加載應(yīng)用。這個特性在開發(fā)的時候痰催,還是很有用的兜辞。

如果你的應(yīng)用并沒有配置這個屬性,想要重新加載一個應(yīng)用夸溶,只需要使用manager里的reload功能就可以逸吵。

有點跑題,回到我們說的delgate上面來缝裁,配置之后扫皱,可以指定Web應(yīng)用類加載時,到底是使用父優(yōu)先還是子優(yōu)先捷绑。

這里的WebappLoader韩脑,就開始了正式的創(chuàng)建WebappClassLoader,而在WebbappClassLoader里具體邏輯如下:判斷已加載的類里是否已經(jīng)包含粹污,然后避免Java SE的classes被覆蓋段多,packageAccess的檢查。之后壮吩,開始了我們的父優(yōu)先子優(yōu)先的流程进苍。這里判斷是否使用delegate時加缘,對于一些容器提供的class,也會跳過觉啊。

boolean delegateLoad = delegate ||filter(name);



而由于上面提到通常delegateLoad這個字段是false拣宏,所以普通我們的Tomcat在web應(yīng)用類加載的時候,都會走上線文加載杠人。最后補充一點勋乾,也是在網(wǎng)上查閱資料看我們常用的Class.forName()這個方法,其實我們往往忽略了該方法還有兩個參數(shù)嗡善,一個是是否必須初始化市俊,另指定加載該類的類加載器,也就是說滤奈,在forName方法中,我也是可以指定獲取該類的類加載器的呀A寐(博主也才發(fā)現(xiàn))

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蜒程,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子伺帘,更是在濱河造成了極大的恐慌昭躺,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件伪嫁,死亡現(xiàn)場離奇詭異领炫,居然都是意外死亡,警方通過查閱死者的電腦和手機张咳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門帝洪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人脚猾,你說我怎么就攤上這事葱峡。” “怎么了龙助?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵砰奕,是天一觀的道長。 經(jīng)常有香客問我提鸟,道長军援,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任称勋,我火速辦了婚禮胸哥,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘赡鲜。我一直安慰自己烘嘱,他們只是感情好昆禽,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蝇庭,像睡著了一般醉鳖。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上哮内,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天盗棵,我揣著相機與錄音,去河邊找鬼北发。 笑死纹因,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的琳拨。 我是一名探鬼主播瞭恰,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼狱庇!你這毒婦竟也來了惊畏?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤密任,失蹤者是張志新(化名)和其女友劉穎颜启,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體浪讳,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡缰盏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了淹遵。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片口猜。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖透揣,靈堂內(nèi)的尸體忽然破棺而出暮的,到底是詐尸還是另有隱情,我是刑警寧澤淌实,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布冻辩,位于F島的核電站,受9級特大地震影響拆祈,放射性物質(zhì)發(fā)生泄漏恨闪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一放坏、第九天 我趴在偏房一處隱蔽的房頂上張望咙咽。 院中可真熱鬧,春花似錦淤年、人聲如沸钧敞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽溉苛。三九已至镜廉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間愚战,已是汗流浹背娇唯。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留寂玲,地道東北人塔插。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像拓哟,于是被迫代替她去往敵國和親想许。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容