《深入理解java虛擬機器》學習筆記6——類載入機制

yangxi_001發表於2013-12-04

Java虛擬機器類載入過程是把Class類檔案載入到記憶體,並對Class檔案中的資料進行校驗、轉換解析和初始化,最終形成可以被虛擬機器直接使用的java型別的過程。

在載入階段,java虛擬機器需要完成以下3件事:

a.通過一個類的全限定名來獲取定義此類的二進位制位元組流。

b.將定義類的二進位制位元組流所代表的靜態儲存結構轉換為方法區的執行時資料結構。

c.在java堆中生成一個代表該類的java.lang.Class物件,作為方法區資料的訪問入口。

Java虛擬機器的類載入是通過類載入器實現的, Java中的類載入器體系結構如下:

(1).BootStrap ClassLoader:啟動類載入器,負責載入存放在%JAVA_HOME%\lib目錄中的,或者通被-Xbootclasspath引數所指定的路徑中的,並且被java虛擬機器識別的(僅按照檔名識別,如rt.jar,名字不符合的類庫,即使放在指定路徑中也不會被載入)類庫到虛擬機器的記憶體中,啟動類載入器無法被java程式直接引用。

(2).Extension ClassLoader:擴充套件類載入器,由sun.misc.Launcher$ExtClassLoader實現,負責載入%JAVA_HOME%\lib\ext目錄中的,或者被java.ext.dirs系統變數所指定的路徑中的所有類庫,開發者可以直接使用擴充套件類載入器。

(3).Application ClassLoader:應用程式類載入器,由sun.misc.Launcher$AppClassLoader實現,負責載入使用者類路徑classpath上所指定的類庫,是類載入器ClassLoader中的getSystemClassLoader()方法的返回值,開發者可以直接使用應用程式類載入器,如果程式中沒有自定義過類載入器,該載入器就是程式中預設的類載入器。

注意:上述三個JDK提供的類載入器雖然是父子類載入器關係,但是沒有使用繼承,而是使用了組合關係。

從JDK1.2開始,java虛擬機器規範推薦開發者使用雙親委派模式(ParentsDelegation Model)進行類載入,其載入過程如下:

(1).如果一個類載入器收到了類載入請求,它首先不會自己去嘗試載入這個類,而是把類載入請求委派給父類載入器去完成。

(2).每一層的類載入器都把類載入請求委派給父類載入器,直到所有的類載入請求都應該傳遞給頂層的啟動類載入器。

(3).如果頂層的啟動類載入器無法完成載入請求,子類載入器嘗試去載入,如果連最初發起類載入請求的類載入器也無法完成載入請求時,將會丟擲ClassNotFoundException,而不再呼叫其子類載入器去進行類載入。

雙親委派 模式的類載入機制的優點是java類它的類載入器一起具備了一種帶優先順序的層次關係,越是基礎的類,越是被上層的類載入器進行載入,保證了java程式的穩定執行。雙親委派模式的實現:
[java] view plaincopy
  1. protected synchronized Class<?> loadClass(String name, Boolean resolve) throws ClassNotFoundException{  
  2.     //首先檢查請求的類是否已經被載入過  
  3.     Class c = findLoadedClass(name);  
  4.     if(c == null){  
  5.     try{  
  6.         if(parent != null){//委派父類載入器載入  
  7.     c = parent.loadClass(name, false);  
  8. }  
  9. else{//委派啟動類載入器載入  
  10.     c = findBootstrapClassOrNull(name);   
  11. }  
  12. }catch(ClassNotFoundException e){  
  13.     //父類載入器無法完成類載入請求  
  14. }  
  15. if(c == null){//本身類載入器進行類載入  
  16.     c = findClass(name);  
  17. }  
  18. }  
  19. if(resolve){  
  20.     resolveClass(c);  
  21. }  
  22. return c;  
  23. }  

若要實現自定義類載入器,只需要繼承java.lang.ClassLoader 類,並且重寫其findClass()方法即可。java.lang.ClassLoader 類的基本職責就是根據一個指定的類的名稱,找到或者生成其對應的位元組程式碼,然後從這些位元組程式碼中定義出一個 Java 類,即 java.lang.Class 類的一個例項。除此之外,ClassLoader 還負責載入 Java 應用所需的資源,如影象檔案和配置檔案等,ClassLoader 中與載入類相關的方法如下:

方法

說明

getParent()

返回該類載入器的父類載入器。

loadClass(String name)

載入名稱為 二進位制名稱為name 的類,返回的結果是 java.lang.Class 類的例項。

 

findClass(String name)

查詢名稱為 name 的類,返回的結果是 java.lang.Class 類的例項。

 

findLoadedClass(String name)

查詢名稱為 name 的已經被載入過的類,返回的結果是 java.lang.Class 類的例項。

 

resolveClass(Class<?> c)

連結指定的 Java 類。

注意:在JDK1.2之前,類載入尚未引入雙親委派模式,因此實現自定義類載入器時常常重寫loadClass方法,提供雙親委派邏輯,從JDK1.2之後,雙親委派模式已經被引入到類載入體系中,自定義類載入器時不需要在自己寫雙親委派的邏輯,因此不鼓勵重寫loadClass方法,而推薦重寫findClass方法。

在Java中,任意一個類都需要由載入它的類載入器和這個類本身一同確定其在java虛擬機器中的唯一性,即比較兩個類是否相等,只有在這兩個類是由同一個類載入器載入的前提之下才有意義,否則,即使這兩個類來源於同一個Class類檔案,只要載入它的類載入器不相同,那麼這兩個類必定不相等(這裡的相等包括代表類的Class物件的equals()方法、isAssignableFrom()方法、isInstance()方法和instanceof關鍵字的結果)。例子程式碼如下:

[java] view plaincopy
  1. package com.test;  
  2.   
  3. public class ClassLoaderTest {  
  4.     public static void main(String[] args)throws Exception{  
  5.         //匿名內部類實現自定義類載入器  
  6.     ClassLoader myClassLoader = new ClassLoader(){  
  7.     protected Class<?> findClass(String name)throws ClassNotFoundException{  
  8.         //獲取類檔名  
  9.     String filename = name.substring(name.lastIndexOf(“.”) + 1) + “.class”;  
  10.     InputStream in = getClass().getResourceAsStream(filename);  
  11.     if(in == null){  
  12.     throw RuntimeException(“Could not found class file:” + filename);  
  13. }  
  14. byte[] b = new byte[in.available()];  
  15. return defineClass(name, b, 0, b.length);  
  16. }catch(IOException e){  
  17.     throw new ClassNotFoundException(name);  
  18. }  
  19. };  
  20. Object obj = myClassLoader.loadClass(“com.test.ClassLoaderTest”).newInstance();  
  21. System.out.println(obj.getClass());  
  22. System.out.println(obj instanceof com.test. ClassLoaderTest);  
  23. }  
  24. }  

輸出結果如下:

com.test.ClassLoaderTest

false

之所以instanceof會返回false,是因為com.test.ClassLoaderTest類預設使用Application ClassLoader載入,而obj是通過自定義類載入器載入的,類載入不相同,因此不相等。

類載入器雙親委派模型是從JDK1.2以後引入的,並且只是一種推薦的模型,不是強制要求的,因此有一些沒有遵循雙親委派模型的特例:

(1).在JDK1.2之前,自定義類載入器都要覆蓋loadClass方法去實現載入類的功能,JDK1.2引入雙親委派模型之後,loadClass方法用於委派父類載入器進行類載入,只有父類載入器無法完成類載入請求時才呼叫自己的findClass方法進行類載入,因此在JDK1.2之前的類載入的loadClass方法沒有遵循雙親委派模型,因此在JDK1.2之後,自定義類載入器不推薦覆蓋loadClass方法,而只需要覆蓋findClass方法即可。

(2).雙親委派模式很好地解決了各個類載入器的基礎類統一問題,越基礎的類由越上層的類載入器進行載入,但是這個基礎類統一有一個不足,當基礎類想要呼叫回下層的使用者程式碼時無法委派子類載入器進行類載入。為了解決這個問題JDK引入了ThreadContext執行緒上下文,通過執行緒上下文的setContextClassLoader方法可以設定執行緒上下文類載入器。

JavaEE只是一個規範,sun公司只給出了介面規範,具體的實現由各個廠商進行實現,因此JNDI,JDBC,JAXB等這些第三方的實現庫就可以被JDK的類庫所呼叫。執行緒上下文類載入器也沒有遵循雙親委派模型。

(3).近年來的熱碼替換,模組熱部署等應用要求不用重啟java虛擬機器就可以實現程式碼模組的即插即用,催生了OSGi技術,在OSGi中類載入器體系被髮展為網狀結構。OSGi也沒有完全遵循雙親委派模型。

相關文章