Java虛擬機詳解(十一)------雙親委派模型


  在上一篇博客,我們介紹了類加載過程,包括5個階段,分別是“加載”,“驗證”,“准備”,“解析”,“初始化”,如下圖所示:

  

 

  本篇博客,我們來介紹Java虛擬機的雙親委派模型,在介紹之前,我先拋出一個問題:

  我們知道,在JDK源碼中,有各種Java自帶的類,比如java.lang.String,java.util.List等,那么我們自己的項目中,能夠寫一個命名為java.lang.String.java 等JDK源碼中存在的類,並且在項目中使用嗎?

1、類加載器

  什么是類加載器?上篇博客我們介紹類加載過程中的第一個階段——加載,作用是“通過一個類的全限定名來獲取描述此類的二進制流”,那么這個加載過程就是由類加載器來完成的。

  從Java虛擬機的角度出發,只存在兩種不同的類加載器,一種是啟動類加載器(Bootstrap ClassLoader),這個類加載器使用 C++ 語言實現,是虛擬機自身的一部分;另一種是所有其它的類加載器,這些類加載器都是由Java語言實現的。但是從Java開發人員的角度來看,類加載器可以細分為如下四種:

①、啟動類加載器(Bootstrap ClassLoader)

  負責將存放在 <JAVA_HOME>/lib 目錄中的,或者被-Xbootclasspath 參數所指定的路徑中的,並且是虛擬機按照文件名識別的(僅按照文件名識別,如rt.jar,名字不符合的類庫即使放在lib目錄中也不會被加載)類庫加載到虛擬機內存中。
  
啟動類加載器無法被Java程序直接引用。

  JDK 中的源碼類大都是由啟動類加載器加載,比如前面說的 java.lang.String,java.util.List等,需要注意的是,啟動類 main Class 也是由啟動類加載器加載。

②、擴展類加載器(Extension ClassLoader)

   這個類加載器由 sun.misc.Launcher$ExtClassLoader 實現,負責加載<JAVA_HOME>/lib/ext 目錄中的,或者被 java.ext.dirs 系統變量所指定的路徑中的所有類庫。

  開發者可以直接使用擴展類加載器。

③、應用程序類加載器(Application ClassLoader)

  由 sun.misc.Launcher$AppClassLoader 實現。由於這個類加載器是 ClassLoader.getSystemClassLoader() 方法的返回值,所以一般也稱它為系統類加載器。

  它負責加載用戶類路徑ClassPath上所指定的類庫,開發者可以直接使用這個類加載器。如果應用程序中沒有自定義過自己的類加載器,一般情況下這個就是程序中默認的類加載器。

   通常項目中自定義的類,都會放在類路徑下,由應用程序類加載器加載。

④、自定義類加載器(User ClassLoader)

   這是由用戶自己定義的類加載器,一般情況下我們不會自定義類加載器,但有些特殊情況,比如JDBC能夠通過連接各種不同的數據庫就是自定義類加載器來實現的,具體用處會在后文詳細介紹。

2、雙親委派模型

  回到文章開頭提出的問題,如果有不法分子在你項目中構造了一個java.lang.String類,並在該類中植入了一些不良代碼,但你自己渾然不知,以為使用的String類還是 rt.jar 包下的,那可能會給你系統造成不良的影響。

  聰明的Java虛擬機實現者也想到了這個問題,於是,他們引入了 雙親委派模型來解決這個問題。

  下面是雙親委派模型的加載流程機制:

  

  總結來說:雙親委派機制就是如果一個類加載器收到了類加載請求,它首先不會自己嘗試去加載這個類,而是把這個請求委派給父類加載器去完成,每一個層次的類加載器都是如此,因此所有的加載請求最終都應該傳送到頂層的啟動類加載器中,只有父類加載器反饋到無法完成這個加載請求(它的搜索范圍沒有找到這個類),子加載器才會嘗試自己去加載。

  其實,這里叫雙親委派可能有點不妥,因為按道理來講只有父加載器,這里的“雙親”是“parents”的直譯,並不表示漢語中的父母雙親。另外,這里的父加載器也不是繼承的關系。

 1 /**
 2  * Create by YSOcean
 3  */
 4 public class ClassLoadTest {
 5     public static void main(String[] args) {
 6         ClassLoader classLoader1 = ClassLoadTest.class.getClassLoader();
 7         ClassLoader classLoader2 = classLoader1.getParent();
 8         ClassLoader classLoader3 = classLoader2.getParent();
 9         System.out.println(classLoader1);
10         System.out.println(classLoader2);
11         System.out.println(classLoader3);
12     }
13 }

  輸出為:

  

  那么知道了什么是雙親委派機制,雙親委派機制有什么好處呢?

  回到上面提出的問題,如果你自定義了一個 java.lang.String類,你會發現這個自定義的String.java可以正常編譯,但是永遠無法被加載運行。因為加載這個類的加載器,會一層一層的往上推,最終由啟動類加載器來加載,而啟動類加載的會是源碼包下的String類,不是你自定義的String類。

3、雙親委派模型實現源碼

  可以打開 java.lang.ClassLoader 類,其 loadClass方法如下:

 1     protected Class<?> loadClass(String name, boolean resolve)
 2         throws ClassNotFoundException
 3     {
 4         synchronized (getClassLoadingLock(name)) {
 5             // First, check if the class has already been loaded
 6             Class<?> c = findLoadedClass(name);
 7             if (c == null) {
 8                 long t0 = System.nanoTime();
 9                 try {
10                     if (parent != null) {
11                         c = parent.loadClass(name, false);
12                     } else {
13                         c = findBootstrapClassOrNull(name);
14                     }
15                 } catch (ClassNotFoundException e) {
16                     // ClassNotFoundException thrown if class not found
17                     // from the non-null parent class loader
18                 }
19 
20                 if (c == null) {
21                     // If still not found, then invoke findClass in order
22                     // to find the class.
23                     long t1 = System.nanoTime();
24                     c = findClass(name);
25 
26                     // this is the defining class loader; record the stats
27                     sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
28                     sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
29                     sun.misc.PerfCounter.getFindClasses().increment();
30                 }
31             }
32             if (resolve) {
33                 resolveClass(c);
34             }
35             return c;
36         }
37     }

  實現方式很簡單,首先會檢查該類是否已經被加載過了,若加載過了直接返回(默認resolve取false);若沒有被加載,則調用父類加載器的 loadClass方法,若父類加載器為空則默認使用啟動類加載器作為父加載器。如果父類加載失敗,則在拋出 ClassNotFoundException 異常后,在調用自己的 findClass 方法進行加載。

4、自定義類加載器

  先說說我們為什么要自定義類加載器?

①、加密

  我們知道Java字節碼是可以進行反編譯的,在某些安全性高的場景,是不允許這種情況發生的。那么我們可以將編譯后的代碼用某種加密算法進行加密,加密后的文件就不能再用常規的類加載器去加載類了。而我們自己可以自定義類加載器在加載的時候先解密,然后在加載。

②、動態創建

  比如很有名的動態代理。

③、從非標准的來源加載代碼

  我們不用非要從class文件中獲取定義此類的二進制流,還可以從數據庫,從網絡中,或者從zip包等。

  明白了為什么要自定義類加載器,接下來我們再來詳述如何自定義類加載器。

  通過第 3 小節的  java.lang.ClassLoader 類的源碼分析,類加載時根據雙親委派模型會先一層層找到父加載器,如果加載失敗,則會調用當前加載器的 findClass() 方法來完成加載。因此我們自定義類加載器,有兩個步驟:

  1、繼承 ClassLoader

  2、覆寫 findClass() 方法

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM