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

  在上一篇博客,咱們介紹了類加載過程,包括5個階段,分別是「加載」,「驗證」,「準備」,「解析」,「初始化」,以下圖所示:html

  

 

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

  咱們知道,在JDK源碼中,有各類Java自帶的類,好比java.lang.String,java.util.List等,那麼咱們本身的項目中,可以寫一個命名爲java.lang.String.java 等JDK源碼中存在的類,而且在項目中使用嗎?算法

一、類加載器

  什麼是類加載器?上篇博客咱們介紹類加載過程當中的第一個階段——加載,做用是「經過一個類的全限定名來獲取描述此類的二進制流」,那麼這個加載過程就是由類加載器來完成的。數據庫

  從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 也是由啓動類加載器加載。this

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

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

  開發者能夠直接使用擴展類加載器。

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

  由 sun.misc.Launcher$AppClassLoader 實現。因爲這個類加載器是 ClassLoader.getSystemClassLoader() 方法的返回值,因此通常也稱它爲系統類加載器。

  它負責加載用戶類路徑ClassPath上所指定的類庫,開發者能夠直接使用這個類加載器。若是應用程序中沒有自定義過本身的類加載器,通常狀況下這個就是程序中默認的類加載器。

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

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

   這是由用戶本身定義的類加載器,通常狀況下咱們不會自定義類加載器,但有些特殊狀況,好比JDBC可以經過鏈接各類不一樣的數據庫就是自定義類加載器來實現的,具體用處會在後文詳細介紹。

二、雙親委派模型

  回到文章開頭提出的問題,若是有不法分子在你項目中構造了一個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類。

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

  能夠打開 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 方法進行加載。

四、自定義類加載器

  先說說咱們爲何要自定義類加載器?

①、加密

  咱們知道Java字節碼是能夠進行反編譯的,在某些安全性高的場景,是不容許這種狀況發生的。那麼咱們能夠將編譯後的代碼用某種加密算法進行加密,加密後的文件就不能再用常規的類加載器去加載類了。而咱們本身能夠自定義類加載器在加載的時候先解密,而後在加載。

②、動態建立

  好比頗有名的動態代理。

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

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

  明白了爲何要自定義類加載器,接下來咱們再來詳述如何自定義類加載器。

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

  一、繼承 ClassLoader

  二、覆寫 findClass() 方法

相關文章
相關標籤/搜索