??ClassLoader在Java中有著非常重要的作用,它主要工作在Class裝載的加載階段,其主要作用是從系統外部獲得Class二進制數據流。ClassLoader是Java的核心組件,所有的Class都是由ClassLoader進行加載的,ClassLoader負責通過各種方式將Class信息的二進制數據流讀入系統,然后交給Java虛擬機進行連接、初始化等操作。因此,ClassLoader在整個裝載階段,只能影響到類的加載,而無法通過ClassLoader去改變類的連接和初始化行為。
??從代碼層次看,ClassLoader是一個抽象類,它提供了一些重要的接口,用于自定義Class的加載流程和加載方式。ClassLoader的主要方法如下:
public Class<?> loadClass(String name) throws ClassNotFoundException
給定一個類名,加載一個類,返回代表這個類的Class實例,如果找不到類,則返回ClassNotFoundException異常protected final Class<?> defineClass(byte[] b,int off,int len)
根據給定的字節碼流b定義一個類,off和len參數表示實際Class信息在byte數組中的位置和長度,其中byte數組b是ClassLoader從外部獲取的。這是受保護的方法,只有在自定義ClassLoader子類中可以使用protected Class<?> findClass(String name) throws ClassNotFoundException
查找一個類,這是一個受保護的方法,也是重載ClassLoader時,重要的系統擴展點。這個方法會在loadClass()時被調用,用于自定義查找類的邏輯。如果不需要修改類加載默認機制,只是想改變類加載的形式就可以重載該方法protected final Class<?> findLoadedClass(String name)
這也是一個受保護的方法,它會去尋找已經加載的類。這個方法是final方法,無法被修改
??在ClassLoader的結構中,還有一個重要的字段parent,它也是一個ClassLoader的實例,這個字段所表示的ClassLoader也稱為這個ClassLoader的雙親。在類加載的過程中,ClassLoader可能會將某些請求交予自己的雙親處理。
ClassLoader的分類
&nbap;?在標準的Java程序中,Java虛擬機會創建3類ClassLoader為整個應用程序服務。它們分別是:BootStrapClassLoader(啟動類加載器)、ExtensionClassLoader(擴展類加載器)和AppClassLoader(應用類加載器,也稱為系統類加載器)。此外,每個應用程序還可以擁有自定義的ClassLoader,擴展Java虛擬機獲取Class數據的能力。
??各個ClassLoader的層次和功能如下圖所示,從ClassLoader的層次自頂往下為啟動類加載器、擴展類加載器、應用類加載器和自定義類加載器。其中,應用類加載器的雙親為擴展類加載器,擴展類加載器的雙親為啟動類加載器。當系統需要使用一個類時,在判斷類是否已經被加載時,會先從當前底層類加載器進行判斷。當系統需要加載一個類時,會從頂層類開始加載,依次向下嘗試,直到成功。
??在這些ClassLoader中,啟動類加載器最為特別,它是完全由C代碼實現的,并且在Java中沒有對象與之對應。系統的核心類就是由啟動類加載器進行加載的,它也是虛擬機的核心組件。擴展類加載器和應用類加載器都有對應的Java對象可供使用。
??無法在Java代碼中直接訪問啟動類加載器,因為這是一個純C實現,因此任何加載在啟動類加載器中的類是無法獲得其ClassLoader實例的,比如:
String.class.getClassLoader()
由于String屬于Java核心類,由啟動類加載器加載,故以上代碼返回的是null。
ClassLoader的雙親委托模式
??系統中的ClassLoader在協同工作時,默認會使用雙親委托模式。即在類加載的時候,系統會判斷當前類是否已經被加載,如果已經被加載,就會直接返回可用的類,否則就會嘗試加載,在嘗試加載時,會先請求雙親處理,如果雙親請求失敗,則會自己加載。
注意:雙親為null有兩種情況:第一,其雙親就是啟動類加載器;第二,當前加載器就是啟動類加載器。判斷類是否加載時,應用類加載器會順著雙親路徑往上判斷,直到啟動類加載器。但是啟動類加載器不會往下詢問,這個委托是單向的。
雙親委托模式的弊端
??前文提到,檢查類是否加載的委托過程是單向的,這個方式雖然從結構上說比較清晰,使各個ClassLoader的職責非常明確,但是同時會帶來一個問題,即頂層的ClassLoader無法訪問底層的ClassLoader所加載的類。
??通常情況下,啟動類加載器中的類為系統核心類,包括一些重要的系統接口,而在應用類加載器中,為應用類。按照這種模式,應用類訪問系統類自然是沒有問題,但是系統類訪問應用類就會出現問題。比如在系統類中提供了一個接口,該接口需要在應用類中得以實現,該接口還綁定一個工廠方法,用于創建該接口的實例,而接口和工廠方法都在啟動類加載器中。這時,就會出現該工廠方法無法創建由應用類加載器加載的應用實例的問題。
雙親委托模式的補充
??在Java平臺中,把核心類(rt.jar)中提供外部服務,可由應用層自行實現的接口,通常可以稱為Service Provider Interface,即SPI。
??為了解決啟動類加載器無法訪問應用類加載器加載的類的問題,Java中通過把一個ClassLoader置于一個線程實例中,這個ClassLoader叫做上下文加載器,使該ClassLoader成為一個相對共享的實例。默認情況下,上下文加載器就是應用類加載器,這樣即使是在啟動類加載器中的代碼也可以通過這種方式訪問應用類加載器的類,其示意圖如下所示:
突破雙親模式
??雙親模式的類加載方式是虛擬機默認的行為,但并非必須這么做,通過重載ClassLoader可以修改該行為。事實上,不少應用軟件和框架都修改了這種行為,比如Tomcat和OSGi框架,都有各自獨特的類加載順序。具體的過程就不嗷述了,感興趣的可以取查閱資料。
熱替換的實現
??熱替換是指在程序的運行過程中,不停止服務,只通過替換程序文件來修改程序的行為。熱替換的關鍵需求在于服務不能中斷,修改必須立即表現正在運行的系統之中。基本上大部分腳本語言都是天生支持熱替換的,比圖PHP,只要替換了PHP源文件,這種改動就會立即生效,而無需重啟Web服務器。
??但對Java來說,熱替換并非天生就支持,如果一個類已經加載到系統中,通過修改類文件,并無法讓系統再來加載并重定義這個類。因此,在Java中實現這一功能的一個可行的方法就是靈活運用ClassLoader。
注意:由不同ClassLoader加載的同名類屬于不同的類型,不能相互轉換和兼容。即兩個不同的ClassLoader加載同一個類,在虛擬機內部,會認為這2個類是完全不同的。
??根據這個特點,可以用來模擬熱替換的實現,基本思路如下圖所示: