深入分析ClassLoader工作機(jī)制
ClassLoader除了能將Class加載到JVM中之外,還有一個重要的作用就是審查每個類應(yīng)該由誰加載急迂,它是一種父優(yōu)先的等級加載機(jī)制。
Classloader類結(jié)構(gòu)分析
我們常會用到或擴(kuò)展ClassLoader,主要會用到如下幾個方法:
ClassLoader
defineClass(byte[],int,int)
findClass(String)
leadClass(String)
resolveClass(Class<?>)
其中defineClass方法用來將byte字節(jié)流解析成JVM能夠是別的Class對象,有了這個方法意味著我們不僅僅可以通過class文件實例化對象慕购,還可以通過其他方式實例化對象,如我們通過網(wǎng)絡(luò)接收到一個類的字節(jié)碼茬底,拿這個字節(jié)碼流直接創(chuàng)建類的實例化對象沪悲。注意,如果直接調(diào)用這個方法生成類的Class對象桩警,這個類的Class對象還沒有resolve可训,這個resolve將會在這個對象真正實例化時才進(jìn)行昌妹。
defineClass通常是和findClass方法一起使用的捶枢,我們通過直接覆蓋ClassLoader父類的findClass方法來實現(xiàn)類的加載規(guī)則,從而取得要加載類的字節(jié)碼飞崖。然后調(diào)用defineClass方法生成類的Class對象烂叔,如果你想在類被加載到JVM中時就被鏈接(Link),那么可以接著調(diào)用另外一個resolveClass方法固歪,當(dāng)然你也可以選擇讓JVM來解決什么時候才鏈接這個類蒜鸡。
如果你不想重新定義加載類的規(guī)則,也沒有復(fù)雜的處理邏輯牢裳,只想在運(yùn)行時能夠加載自己指定的一個類逢防,那么你可以用this.getClass().getClassLoader().loadClass("class")調(diào)用ClassLoader的loadClass方法以獲取這個類的Class對象,這個loadClass還有重載方法蒲讯,你同樣可以決定在什么時候解析這個類忘朝。
ClassLoader是個抽象類,它還有很多子類判帮,我們?nèi)绻獙崿F(xiàn)自己的ClassLoader局嘁,一般都會繼承URLClassLoader這個子類溉箕,因為這個類已經(jīng)幫我們實現(xiàn)了大部分工作,我們只需要在適當(dāng)?shù)牡胤阶鲂┬薷木托小?/p>
ClassLoader的等級加載機(jī)制(上級委托接待機(jī)制)
如何保證不同等級的會員通過不同的會員接待室進(jìn)入會場悦昵?保證每個會員不會走錯接待室肴茄,并且每個會員只能被一個接待室接待,從而保持接待的一致性但指。如何設(shè)計這個接待規(guī)則寡痰?
ClassLoader就設(shè)計了這樣一種接待機(jī)制,就是上級委托接待機(jī)制棋凳。具體是這樣的:任何一個會員到達(dá)任何一個接待室時氓癌,這個接待室首先會檢查這個會員是否已經(jīng)被自己接待過,如果接待過贫橙,那么拒絕本次接待贪婉,也就是不再發(fā)入會證明了,如果沒有接待過卢肃,那么會向上詢問這個會員是否應(yīng)該在上一級的更高級別的接待室接待疲迂,上級接待室會根據(jù)它們的接待規(guī)則,檢查這個會員是否已經(jīng)被接待過莫湘,如果已經(jīng)接待過尤蒿,同樣的處理方式,將已經(jīng)接待過的結(jié)果反饋給下一級幅垮,如果也沒有接待過腰池,再向更高一級接待室轉(zhuǎn)發(fā)接待請求,更高一級也還是同樣的處理方法忙芒,直到有一級接待室接待或者告訴它下一級這個會員不是自己接待的這個結(jié)果示弓;如果這個會員來到的這個接待室得到它上一級的接待室反饋認(rèn)為這個會員沒有被接待,并且也不應(yīng)該有它們接待呵萨,這個接待室會正式接待這個會員奏属,并發(fā)給他入會證明,這個會員就會被定義為這個接待室等級的會員潮峦。
整個JVM平臺提供三層ClassLoader囱皿,這三層ClassLoader可以分為兩種類型,可以理解為--->為接待室服務(wù)的接待室和為會員服務(wù)的接待室兩種忱嘹。
(1)Bootstrap ClassLoader嘱腥,這個ClassLoader就是接待室服務(wù)自身的,它主要加載JVM自身工作需要的類拘悦。這個ClassLoader完全是由JVM自己控制的齿兔,需要加載哪個類,怎么加載都有JVM自己控制,別人也訪問不到這個類愧驱,所以這個ClassLoader是不遵守前面介紹的加載規(guī)則的慰技,它僅僅是一個類的加載工具而已,既沒有更高一級的父加載器组砚,也沒有子加載器吻商。
(2)ExtClassloader,這個類加載器有點特殊糟红,它是JVM自身的一部分艾帐,但是它的血統(tǒng)也不是很純正,它并不是JVM親自實現(xiàn)的盆偿,我們可以理解為這個類加載器是那些與這個大會合作單位的員工會員柒爸,這些會員既不是JVM內(nèi)部的,也和普通的外部會員不同事扭,所以就由這個類加載器來加載捎稚,它服務(wù)的特定目標(biāo)在System.getProperty("java.ext.dirs")目錄下。
(3)AppClassLoader求橄,這個類加載器就是專門為接待會員服務(wù)的今野,它的父類是ExtClassLoader。它服務(wù)的目標(biāo)是廣大普通會員罐农,所有在System.,getProperty("java.class.path")目錄下的類都可以被這個類加載器加載条霜,這個目錄就是我們常用到的classpath。
JVM加載class文件到內(nèi)存有兩種方式:
(1) 隱式加載:所謂隱式加載就是不通過在代碼里調(diào)用ClassLoader來加載需要的類涵亏,而是通過JVM來自動加載需要的類到內(nèi)存的方式宰睡。例如,當(dāng)我們在類中繼承或者引用某個類時气筋,JVM在解析當(dāng)前這個類時發(fā)現(xiàn)引用的類不在內(nèi)存中拆内,那么就會自動將這些類加載到內(nèi)存中。
(2)顯示加載:相反的顯示加載就是我們在代碼中通過ClassLoader類來加載一個類的方式裆悄。
如何加載class文件
ClassLoader加載一個class文件到JVM時需要經(jīng)過的步驟矛纹。
第一個階段是找到.class文件并把這個文件包含的字節(jié)碼加載到內(nèi)存中臂聋。
第二個階段又可以分為三個步驟光稼,分別是字節(jié)碼驗證、Class類數(shù)據(jù)結(jié)構(gòu)分析及相應(yīng)的內(nèi)存分配和最后的符號表的鏈接孩等。
第三階段是類中靜態(tài)屬性和初始化賦值艾君,以及靜態(tài)塊的執(zhí)行。
加載字節(jié)碼到內(nèi)存 其實在ClassLoader抽象類中并沒有定義如何去加載肄方,如何去找到指定類并把它的字節(jié)碼加載到內(nèi)存冰垄。需要實現(xiàn)findClass()方法。子類URLClassLoader是如何實現(xiàn)findClass()的权她,在URLClassLoader中通過一個URLClassPath類取得要加載的class文件字節(jié)流虹茶,而這個URLClassPath定義了到哪里去找這個class文件逝薪,如果找到了class文件,再讀取它的byte字節(jié)流蝴罪,通過調(diào)用defineClass()方法來創(chuàng)建類對象董济。
</p>
常見加載類錯誤分析
在執(zhí)行Java程序是經(jīng)常會碰到ClassNotFoundException和NoClassDefFoundError兩個異常,它們都和類加載有關(guān)要门。
(1) ClassNotFoundException
當(dāng)JVM要加載指定文件的字節(jié)碼到內(nèi)存時虏肾,并沒有找到這個文件對應(yīng)的字節(jié)碼。解決的辦法就是檢查在當(dāng)前的classpath目錄下有沒有指定的文件的存在欢搜,如果不知道當(dāng)前classpath路徑封豪,可以通過命令獲取:
this.getClass().getClassLoader().getResource("").toString()
(2) NoClassDefFoundError
常出現(xiàn)在第一次使用命令執(zhí)行java類 ???例如:
java -cp example.jar Example
這里是因為在命令行中沒有加類的包名???正確的寫法:
java -cp example.jar net.aaa.Example
(3) UnstatusfiledLinkError
(4) ClassCastException
(5) ExceptionInInitializerError