1. 程式人生 > >再次深入理解類載入機制(一)

再次深入理解類載入機制(一)

一、類的載入方法

1、ClassLoader的的基本概念:

       與c與c++編寫的程式不同,Java程式並不是可執行檔案,而是有許多的類檔案組成,每個檔案對應一個Java類。而且這些類並不是全部裝進記憶體,而是根據程式執行的需要逐步裝載。ClassLoader是JVM的實現的一部分。

2、ClassLoader的載入流程

       當執行第一個程式的時候JVM啟動,執行bootstrap classloader,該載入器負責載入核心Java API,然後呼叫ExtClassLoader載入擴充套件API,最後AppClassLoader載入CLASSPATH目錄下面定義的Class。

首先我們應該要知道ClassLoader除了將Class載入到JVM中之外,還有一個重要的作用就是審查每個類應該由誰進行載入,這是一種父類優先的機制。

除了這兩個作用還有就是將Class位元組碼重新解析成JVM統一要求的格式。

下面是ClassLoader類的loadClass方法:

Java程式碼  收藏程式碼
  1.    protected synchronized Class<?> loadClass(String name, boolean resolve)  
  2. throws ClassNotFoundException  
  3.    {  
  4. // First, check if the class has already been loaded  
  5. Class c = findLoadedClass(name);  
  6.        //該類還沒有被載入,那麼則  
  7. if (c == null) {  
  8.     try {  
  9.                //如其父類不為空,那麼則使用其父類進行載入(父類委託機制)  
  10.     if (parent != null) {  
  11.         c = parent.loadClass(name, false);  
  12.     }   
  13.                //如果其父類為空則使用BootstrapClass進行載入  
  14.                else {  
  15.         c = findBootstrapClass0(name);  
  16.     }  
  17.     } catch (ClassNotFoundException e) {  
  18.         // If still not found, then invoke findClass in order  
  19.         // to find the class.  
  20.                //如果此類的父類載入器無法載入該類,那麼有此類載入器的findClass進行類位元組碼                  的載入  
  21.         c = findClass(name);  
  22.     }  
  23. }  
  24.        //是否需要解析類,如果resolve=true時,則保證已經裝載,而且已經連線了。resolve=falses時,則僅僅是去裝載這個類,不關心是否連線了,所以此時可能被連線了,也可能沒有被連線  
  25. if (resolve) {  
  26.     resolveClass(c);  
  27. }  
  28. return c;  
  29.    }  

 上面的程式碼可以看出,一個類的載入過程使用了一種父類委託的模式,這種模式的好處主要有兩種:

1、可以避免重複載入,當父類已經載入了該類的時候就沒有必要子ClassLoader再載入一次。

2、考慮到安全因素,如果不使用這種委託模式,那麼可以隨時使用自定義的String來動態代替Java核心API中定義的型別,這樣做會存在非常大的安全隱患,而父類委託模式就可以避免這種情況,因為服載入器已經在啟動時被載入,所以,使用者自定義類是無法載入一個自定義的String的。

 除了上面的loadClass方法外方法還有幾個比較重要的 

Java程式碼  收藏程式碼
  1. protected final Class<?> defineClass  
  2. protected Class<?> findClass(String name)  
  3. protected final void resolveClass(Class<?> c)   

上面的loadClass方法的作用只是啟動類的載入,指定類應該由誰進行載入。而具體的怎麼載入需要用到

defineClass方法:其能夠將byte位元組流解析成JVM能夠識別的Class物件。

然後還有一個findClass方法:

findClass:負責取得需要載入的類的位元組碼,然後可以呼叫definedClass方法生成類的Class物件。

有了這樣兩個方法我們不僅僅可以通過class檔案例項化物件,還可以通過網路接受位元組碼例項化物件。

一般我們配合使用defineClass和findClass 方法,直接覆蓋ClassLoader父類的findClass方法來實現類的載入規則,findClass取得需要載入的類的位元組碼,defineClass根據位元組碼建立類物件

 破壞雙親委派模型

雙親委派模型第一次被破壞:

    雙親委派模型在jdk1.2的時候才被引入,提供了findClass方法供使用者進行重寫以指定自己的類載入規則,但是在jdk1.2之前使用者繼承ClassLoader的目的就是為了重寫loadClass方法,但是雙親委派的具體邏輯就實現在這個方法之中,所以在JDK1.2之後就不提倡使用者覆蓋loadClass方法,而應當把自己得把類載入邏輯解除安裝findClass方法中,如果父類載入其載入失敗會呼叫自己的findClass進行類的載入,這樣就可以保證心寫出來的類載入器符合雙親委派模型。

雙親委派模型第二次被破壞:

 雙親委派模型的第二次破壞是由這個模型本身造成的,這個模型中是越基礎的類越由上層的載入器載入,之所以基礎是因為他們總是被作為使用者呼叫的API但是事無絕對,如果基礎類又要呼叫使用者的程式碼怎麼辦???

    一個典型的例子就是JNDI,他的程式碼由啟動類進行載入,但是JNDI的目的是對資源進行集中管理和查詢,他需要調下用呼叫獨立廠商實現並部署在應用程式ClassPath

下的JNDI介面提供者,這個問題的解決是在程式中引入了不太優雅的設計:執行緒上下文類載入器,JNDI可以使用這個執行緒上下文類載入器去載入所需要的SPI,也就是父類載入器請求自類載入器去完成類載入的動作,這已經違反了雙親委派模型的一般性原則,在Java中所有涉及SPI的如jNDI  ,   JDBC  , JCE ,  JAXB和JBI都使用這種模型來進行類的載入。

 雙親委派模型第三次被破壞:

這裡主要是說OSGI為了實現Java模組化熱部署而自定義的類載入機制的實現。它把雙親委派模型的樹狀結構改編成了自己的更加複雜的網狀結構,具體的這裡已經暫時超過了小菜的討論範圍