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))