天天看點

ClassLoader類加載機制

一、類加載器

  類加載器(ClassLoader),顧名思義,即加載類的東西。在我們使用一個類之前,JVM需要先将該類的位元組碼檔案(.class檔案)從磁盤、網絡或其他來源加載到記憶體中,并對位元組碼進行解析生成對應的Class對象,這就是類加載器的功能。我們可以利用類加載器,實作類的動态加載。

二、類的加載機制

  在Java中,采用雙親委派機制來實作類的加載。那什麼是雙親委派機制?

從以上描述中,我們可以總結出如下四點:

1、類的加載過程采用委托模式實作

2、每個 ClassLoader 都有一個父加載器。

3、類加載器在加載類之前會先遞歸的去嘗試使用父加載器加載。

4、虛拟機有一個内建的啟動類加載器(bootstrap ClassLoader),該加載器沒有父加載器,但是可以作為其他加載器的父加載器。

Java 提供三種類型的系統類加載器。

  第一種是啟動類加載器,由C++語言實作,屬于JVM的一部分,其作用是加載 <Java_Runtime_Home>/lib 目錄中的檔案,并且該類加載器隻加載特定名稱的檔案(如 rt.jar),而不是該目錄下所有的檔案。(除了指定目錄外還必須是特定名稱的檔案才加載。)

  另外兩種是 Java 語言自身實作的類加載器,包括擴充類加載器(ExtClassLoader)和應用類加載器(AppClassLoader),擴充類加載器負責加載<Java_Runtime_Home>\lib\ext目錄中或系統變量 java.ext.dirs 所指定的目錄中的檔案。

  應用程式類加載器負責加載使用者類路徑中的檔案。使用者可以直接使用擴充類加載器或系統類加載器來加載自己的類,但是使用者無法直接使用啟動類加載器,

  除了這兩種類加載器以外,使用者也可以自定義類加載器。

  加載流程如下圖所示:

  

ClassLoader類加載機制

注意:這裡父類加載器并不是通過繼承關系來實作的,而是采用組合實作的。見下圖一

啟動類加載器屬于 JVM 的一部分,它不是由 Java 語言實作的,在 Java 中無法直接引用,傳回空。雙親委派機制仍舊有效,見下圖二

ClassLoader類加載機制

從源碼可以看出,ExtClassLoader 和 AppClassLoader都繼承自 ClassLoader 類,ClassLoader 類中通過 loadClass 方法來實作雙親委派機制。

整個類的加載過程可分為如下三步:

  1、查找對應的類是否已經加載。

  2、若未加載,則判斷目前類加載器的父加載器是否為空,不為空則委托給父類去加載,否則調用啟動類加載器加載(findBootstrapClassOrNull 再往下會調用一個 native 方法)。

  3、若第二步加載失敗,則調用目前類加載器加載。

  通過上面這段程式,可以很清楚的看出擴充類加載器與啟動類加載器之間是如何實作委托模式的。

 自定義加載器的注意事項:

通常情況下,我們都是直接使用系統類加載器。但是,有的時候,我們也需要自定義類加載器。比如應用是通過網絡來傳輸 Java 類的位元組碼,為保證安全性,這些位元組碼經過了加密處理,這時系統類加載器就無法對其進行加載,這樣則需要自定義類加載器來實作。自定義類加載器一般都是繼承自 ClassLoader 類,從上面對 loadClass 方法來分析來看,我們隻需要重寫 findClass 方法即可。 

自定義類加載器的核心在于對位元組碼檔案的擷取,如果是加密的位元組碼則需要在該類中對檔案進行解密。

有幾點需要注意:

  1、這裡傳遞的檔案名需要是類的全限定性名稱,即com.tt.b2b.test.Test格式的,因為 defineClass 方法是按這種格式進行處理的。

  2、最好不要重寫loadClass方法,因為這樣容易破壞雙親委托模式。

  3、這類 Test 類本身可以被 AppClassLoader 類加載,是以我們不能把 com/tt/b2b/test/Test.class 放在類路徑下。否則,由于雙親委托機制的存在,會直接導緻該類由 AppClassLoader 加載,而不會通過我們自定義類加載器來加載。

  雙親委派機制能很好地解決類加載的統一性問題。對一個 Class 對象來說,如果類加載器不同,即便是同一個位元組碼檔案,生成的 Class 對象也是不等的。也就是說,類加載器相當于 Class 對象的一個命名空間。雙親委派機制則保證了基類都由相同的類加載器加載,這樣就避免了同一個位元組碼檔案被多次加載生成不同的 Class 對象的問題。但雙親委派機制僅僅是Java 規範所推薦的一種實作方式,它并不是強制性的要求。