類加載器(ClassLoader)用來加載 class字節(jié)碼到 Java 虛擬機(jī)中。一般來說,Java 虛擬機(jī)使用 Java 類的方式如下:Java 源文件在經(jīng)過 Javac之后就被轉(zhuǎn)換成 Java 字節(jié)碼文件(.class 文件)。類加載器負(fù)責(zé)讀取 Java 字節(jié)代碼,并轉(zhuǎn)換成 java.lang.Class 類的一個實例。每一個這樣的實例用來表示一個 Java 類。實際的情況可能更加復(fù)雜,比如 Java 字節(jié)代碼可能是通過工具動態(tài)生成的,也可能是通過網(wǎng)絡(luò)下載。 類與類加載器 類加載器雖然只用于實現(xiàn)類的加載動作,但它在Java程序中起到的作用卻遠(yuǎn)遠(yuǎn)不限于類加載階段。對于任意一個類,都需要由加載它的類加載器和這個類本身一同確立其在Java虛擬中的唯一性。說通俗一些,比較兩個類是否“相等”,只有在兩個類是由同一個類加載器的前提之下才有意義,否則,即使這兩個類來源于同一個class文件,只要加載它的類加載器不同,那這兩個類必定不相等。這里所指的“相等”包括代表類的Class對象的equal方法、isAssignableFrom()、isInstance()方法及instance關(guān)鍵字返回的結(jié)果。 類加載器分類: 主要分為Bootstrap ClassLoader、Extension ClassLoader、Application ClassLoader和User Defined ClassLoader。 啟動類加載器(Bootstrap ClassLoader): 這個類加載器使用C++語言實現(xiàn),并非ClassLoader的子類。主要負(fù)責(zé)加載存放在JAVA_HOME / jre / lib / rt.jar里面所有的class文件,或者被-Xbootclasspath參數(shù)所指定路徑中以rt.jar命名的文件。 擴(kuò)展類加載器(Extension ClassLoader): 這個加載器由sun.misc.Launcher$ExtClassLoader實現(xiàn),它負(fù)責(zé)加載AVA_HOME / lib / ext目錄中的,或者被java.ext.dirs系統(tǒng)變量所指定的路徑中的所有類庫。 應(yīng)用程序類加載器(Application ClassLoader): 這個加載器由sun.misc.Launcher$AppClassLoader實現(xiàn),它負(fù)責(zé)加載classpath對應(yīng)的jar及目錄。一般情況下這個就是程序中默認(rèn)的類加載器。 自定義類加載器(User Defined ClassLoader): 開發(fā)人員繼承ClassLoader抽象類自行實現(xiàn)的類加載器,基于自行開發(fā)的ClassLoader可用于并非加載classpath中(例如從網(wǎng)絡(luò)上下載的jar或二進(jìn)制字節(jié)碼)、還可以在加載class文件之前做些小動作 如:加密等。 雙親委托模型: 上圖中所展示的類加載器之間的這種層次關(guān)系,就稱為類加載器的雙親委托模型。雙親委托模型要求除了頂層的啟動類加載器外,其余的類加載器都應(yīng)當(dāng)有自己的父類加載器。這里類加載器之間的父子關(guān)系一般不會以繼承的關(guān)系來實現(xiàn),而是使用組合關(guān)系來復(fù)用父加載器的代碼。
雙親委托的工作過程:如果一個類加載器收到了一個類加載請求,它首先不會自己去加載這個類,而是把這個請求委托給父類加載器去完成,每一個層次的類加載器都是如此,因此所有的加載請求最終都應(yīng)該傳送到頂層的啟動類加載器中,只有當(dāng)父類加載器反饋自己無法完成加載請求(它管理的范圍之中沒有這個類)時,子加載器才會嘗試著自己去加載。 使用雙親委托模型來組織類加載器之間的關(guān)系,有一個顯而易見的好處就是Java類隨著它的類加載器一起具備了一種帶有優(yōu)先級的層次關(guān)系,例如java.lang.Object存放在rt.jar之中,無論那個類加載器要加載這個類,最終都是委托給啟動類加載器進(jìn)行加載,因此Object類在程序的各種類加載器環(huán)境中都是同一個類,相反,如果沒有雙親委托模型,由各個類加載器去完成的話,如果用戶自己寫一個名為java.lang.Object的類,并放在classpath中,應(yīng)用程序中可能會出現(xiàn)多個不同的Object類,java類型體系中最基本安全行為也就無法保證。 類加載器SPI: java.lang.ClassLoader 類提供的幾個關(guān)鍵方法: loadClass: 此方法負(fù)責(zé)加載指定名字的類,首先會從已加載的類中去尋找,如果沒有找到;從parent ClassLoader[ExtClassLoader]中加載;如果沒有加載到,則從Bootstrap ClassLoader中嘗試加載(findBootstrapClassOrNull方法), 如果還是加載失敗,則拋出異常ClassNotFoundException, 在調(diào)用自己的findClass方法進(jìn)行加載。如果要改變類的加載順序可以覆蓋此方法;如果加載順序相同,則可以通過覆蓋findClass方法來做特殊處理,例如:解密,固定路徑尋找等。當(dāng)通過整個尋找類的過程仍然未獲取Class對象,則拋出ClassNotFoundException異常。 如果類需要resolve,在調(diào)用resolveClass進(jìn)行鏈接。
findLoadedClass 此方法負(fù)責(zé)從當(dāng)前ClassLoader實例對象的緩存中尋找已加載的類,調(diào)用的為native方法。
findClass 此方法直接拋出ClassNotFoundException異常,因此要通過覆蓋loadClass或此方法來以自定義的方式加載相應(yīng)的類。
findSystemClass 此方法是從sun.misc.Launcher$AppClassLoader中尋找類,如果未找到,則繼續(xù)從BootstrapClassLoader中尋找,如果仍然未找到,返回null
defineClass 此方法負(fù)責(zé)將二進(jìn)制字節(jié)流轉(zhuǎn)換為Class對象,這個方法對于自定義類加載器而言非常重要。如果二進(jìn)制的字節(jié)碼的格式不符合jvm class文件格式規(guī)范,則拋出ClassFormatError異常;如果生成的類名和二進(jìn)制字節(jié)碼不同,則拋出NoClassDefFoundError;如果加載的class是受保護(hù)的、采用不同簽名的,或者類名是以java.開頭的,則拋出SecurityException異常。
resolveClass 此方法負(fù)責(zé)完成Class對象的鏈接,如果鏈接過,則直接返回。 常見異常: ClassNotFoundException 這是最常見的異常,產(chǎn)生這個異常的原因為在當(dāng)前的ClassLoader 中加載類時,未找到類文件, NoClassDefFoundError 這個異常是因為 加載到的類中引用到的另外類不存在,例如要加載A,而A中盜用了B,B不存在或當(dāng)前的ClassLoader無法加載B,就會拋出這個異常。 LinkageError 該異常在自定義ClassLoader的情況下更容易出現(xiàn),主要原因是此類已經(jīng)在ClassLoader加載過了,重復(fù)的加載會造成該異常。 本系列:
關(guān)注「ImportNew」 看更多 Java 技術(shù)精選文章 ↓↓↓
|
|