類加載器簡介
在介紹雙親委托模型之前,先介紹一下類加載器。類加載器通過一個類的全限定名來轉換為描述這個類的二進制字節(jié)流。
對于任意一個類,被同一個類加載器加載后都是唯一的,但如果被不同加載器加載后,就不是唯一的了。即使是源于同一個Class文件、被同一個JVM加載,只要加載類的加載器不同,那么類就不同。
如何判斷類是否相同,可以使用Class對象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回結果進行判斷,也可以使用instanceof關鍵字進行對象所屬關系的判斷。
下面我們寫一個不同類加載器加載后的類,看一下對instanceof關鍵字運算有什么影響:
public class OneMoreStudy { public static void main(String[] args) throws
Exception { ClassLoader myLoader = new ClassLoader() { @Override public
Class<?> loadClass(String name) throws ClassNotFoundException { try { String
fileName = name.substring(name.lastIndexOf(".") + 1) + ".class"; InputStream
inputStream = getClass().getResourceAsStream(fileName); if (inputStream ==
null) { return super.loadClass(name); } byte[] array = new
byte[inputStream.available()]; inputStream.read(array); return
defineClass(name, array, 0, array.length); } catch (IOException e) { throw new
ClassNotFoundException(name); } } }; Object object =
myLoader.loadClass("OneMoreStudy").newInstance(); System.out.println("class
name: " + object.getClass().getName()); System.out.println("instanceof: " +
(object instanceof OneMoreStudy)); } }
運行結果:
class name: OneMoreStudy instanceof: false
在運行結果中,第一行可以看出這個對象確實是OneMoreStudy類實例化出來的,但在第二行中instanceof運算結果是false,說明在JVM中存在兩個
OneMoreStudy
類,一個是由系統(tǒng)應用程序類加載器加載的,另一個是由我們自定義的類加載器加載的。雖然都是來自同一個Class文件,在同一個JVM里,但是被不同的類加載器加載后,仍然是兩個獨立的類。
歡迎關注微信公眾號:萬貓學社,每周一分享Java技術干貨。
類加載器的劃分
除了像上面例子代碼中,我們自己實現(xiàn)的自定義類加載器,還有3種系統(tǒng)提供的類加載器:
*
啟動類加載器(Bootstrap
ClassLoader):它負責將存放在%JAVA_HOME%\lib目錄中的,或者被-Xbootclasspath參數(shù)所指定的路徑中的,并且是JVM識別的類庫加載到JVM內(nèi)存中。它僅按照文件名識別,如rt.jar,名字不符合的類庫即使放在lib目錄中也不會被加載。它是由C++語言實現(xiàn)的,無法被Java程序直接引用。
*
擴展類加載器(Extension
ClassLoader):它負責加載%JAVA_HOME%\lib\ext目錄中的,或者被java.ext.dirs系統(tǒng)變量所指定的路徑中的所有類庫。它由sun.misc.Launcher.ExtClassLoader實現(xiàn),開發(fā)者可以直接使用擴展類加載器。
*
應用程序類加載器(Application
ClassLoader):它負責加載用戶類路徑(ClassPath)上所指定的類庫。由于它是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也稱它為
系統(tǒng)類加載器
。它由sun.misc.Launcher.AppClassLoader來實現(xiàn),開發(fā)者可以直接使用這個類加載器,如果應用程序中沒有自定義過自己的類加載器,一般情況下這個就是程序中默認的類加載器。
歡迎關注微信公眾號:萬貓學社,每周一分享Java技術干貨。
雙親委托模型
之前提到,對于任意一個類,都需要由加載它的類加載器和這個類本身一同確立其在JVM中的唯一性。可是有這么多種的類加載器,如何保證一個類在JVM中的唯一性呢?為了解決這個問題,雙親委托模型(Parents
Delegation Model)應運而生,它就是下圖所展示的類加載器之間的層次關系:
除了頂層的啟動類加載器外,其余的類加載器都必須有自己的父類加載器。類加載器之間的父子關系,一般不會以繼承的關系來實現(xiàn),而是都使用組合關系來復用父類加載器。
類加載器收到類加載的請求后,它不會首先自己去嘗試加載這個類,而是把這個請求委派給父類加載器去嘗試加載。每一個類加載器都是如此,因此所有的加載請求最終都應該傳送到頂層的啟動類加載器中。只有當父類加載器反饋自己無法完成這個加載請求(它的搜索范圍中沒有找到所需的類)時,子加載器才會嘗試自己去加載。這樣就保證了類在JVM中的唯一性,也保證了Java程序穩(wěn)定運作。
實現(xiàn)雙親委派模型的代碼都集中在java.lang.ClassLoader的loadClass()方法之中,如下:
protected Class<?> loadClass(String name, boolean resolve) throws
ClassNotFoundException { synchronized (getClassLoadingLock(name)) {
//首先,檢查該類是否已經(jīng)被加載過了 Class<?> c = findLoadedClass(name);
//如果沒有加載過,就調(diào)用父類加載器的loadClass()方法 if (c == null) { long t0 = System.nanoTime();
try { if (parent != null) { c = parent.loadClass(name, false); } else {
//如果父類加載器為空,就使用啟動類加載器 c = findBootstrapClassOrNull(name); } } catch
(ClassNotFoundException e) { //如果在父類加載器中找不到該類,就會拋出ClassNotFoundException } if
(c == null) { //如果父類找不到,就調(diào)用findClass()來找到該類。 long t1 = System.nanoTime(); c =
findClass(name); //記錄統(tǒng)計數(shù)據(jù)
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) {
resolveClass(c); } return c; } }
歡迎關注微信公眾號:萬貓學社,每周一分享Java技術干貨。
破壞雙親委派模型
雙親委派模型并不是一個強制性的約束模型,而是Java設計者們推薦給開發(fā)者們的類加載器實現(xiàn)方式。大部分的類加載器都遵循這個模型,但也有例外的情況,比如下面這三種情況:
重寫ClassLoader的loadClass()方法
在上面例子代碼中,就是重寫了ClassLoader的loadClass()方法,破壞了雙親委派模型,產(chǎn)生了不唯一的類。所以,不提倡開發(fā)人員覆蓋loadClass()方法,而應當把自己的類加載邏輯寫到findClass()方法中,在loadClass()方法的邏輯里如果父類加載失敗,則會調(diào)用自己的findClass()方法來完成加載,這樣就可以保證新寫出來的類加載器是符合雙親委派模型。
歡迎關注微信公眾號:萬貓學社,每周一分享Java技術干貨。
SPI(服務提供者接口)
Java提供了很多SPI(Service Provider
Interface,服務提供者接口),允許第三方為這些接口提供實現(xiàn),常見的SPI有JDBC、JNDI、JCE、JAXB和JBI等。
SPI的接口由Java核心庫來提供,而這些SPI的實現(xiàn)代碼則是作為Java應用所依賴的jar包被包含進類路徑(ClassPath)里。SPI接口中的代碼經(jīng)常需要加載具體的實現(xiàn)類。那么問題來了,SPI的接口是Java核心庫的一部分,是由
啟動類加載器來加載的;SPI的實現(xiàn)類是由系統(tǒng)類加載器來加載的。引導類加載器是無法找到SPI的實現(xiàn)類的,因為依照雙親委派模型,啟動類加載器無法委派系統(tǒng)類加載器
來加載類。
這時候就會使用線程上下文類加載器(Thread Context
ClassLoader),在JVM中會把當前線程的類加載器加載不到的類交給線程上下文類加載器來加載,直接使用Thread.currentThread().getContextClassLoader()來獲得,默認返回的就是應用程序類加載器,也可以通過java.lang.Thread類的setContextClassLoader()方法進行設置。
而線程上下文類加載器破壞了雙親委派模型,也就是父類加載器請求子類加載器去完成類加載的動作,但為了實現(xiàn)功能,這也是一種巧妙的實現(xiàn)方式。
歡迎關注微信公眾號:萬貓學社,每周一分享Java技術干貨。
OSGi(開放服務網(wǎng)關協(xié)議)
OSGi(Open Service Gateway
Initiative,開放服務網(wǎng)關協(xié)議)技術是面向Java動態(tài)化模塊化系統(tǒng)模型,程序模塊(稱為Bundle)無需重新引導可以被遠程安裝、啟動、升級和卸載。實現(xiàn)程序模塊熱部署的關鍵則是它自定義的類加載器機制的實現(xiàn)。
在OSGi中,類加載器不再是雙親委派模型中的樹狀結構,而是一個較為復雜的網(wǎng)狀結構,類加載的規(guī)則簡要介紹如下:
* 若類屬于java.*包,則將加載請求委托給父加載器
* 若類定義在啟動委托列表(org.osgi.framework.bootdelegation)中,則將加載請求委托給父加載器
*
若類屬于在Import-Package中定義的包,則框架通過ClassLoader依賴關系圖找到導出此包的Bundle的ClassLoader,并將加載請求委托給此ClassLoader
*
若類資源屬于在Require-Bundle中定義的Bundle,則框架通過ClassLoader依賴關系圖找到此Bundle的ClassLoader,將加載請求委托給此ClassLoader
* Bundle搜索自己的類資源( 包括Bundle-Classpath里面定義的類路徑和屬于Bundle的Fragment的類資源)
* 若類在DynamicImport-Package中定義,則開始嘗試在運行環(huán)境中尋找符合條件的Bundle
如果在經(jīng)過上面一系列步驟后,仍然沒有正確地加載到類資源,則會向外拋出類未發(fā)現(xiàn)異常。
歡迎關注微信公眾號:萬貓學社,每周一分享Java技術干貨。
總結
類加載器通過一個類的全限定名來轉換為描述這個類的二進制字節(jié)流,可劃分為啟動類加載器、擴展類加載器、應用程序類加載器、自定義類加載器。在雙親委托模型
中,將上述各種類加載器組成一系列的父子關系,子類加載器先把類加載請求委派給父類加載器去嘗試加載,父類加載器無法加載時子類加載器才自己嘗試加載,這樣保證了類在JVM中的唯一性。不過,也不遵循雙親委托模型的情況,比如:重寫ClassLoader的loadClass()方法、SPI(服務提供者接口)、OSGi(開放服務網(wǎng)關協(xié)議)。
歡迎關注微信公眾號:萬貓學社,每周一分享Java技術干貨。
熱門工具 換一換