1. 程式人生 > >ClassLoader的工作原理

ClassLoader的工作原理

轉載至:http://sys53.iteye.com/blog/622626

每個執行中的執行緒都有一個成員contextClassLoader,用來在執行時動態地載入其它類 
系統預設的contextClassLoader是systemClassLoader,所以一般而言java程式在執行時可以使用JVM自帶的類、$JAVA_HOME/jre/lib/ext/中的類和$CLASSPATH/中的類 
可以使用Thread.currentThread().setContextClassLoader(...);更改當前執行緒的contextClassLoader,來改變其載入類的行為

ClassLoader被組織成樹形,一般的工作原理是: 
1) 執行緒需要用到某個類,於是contextClassLoader被請求來載入該類 
2) contextClassLoader請求它的父ClassLoader來完成該載入請求 
3) 如果父ClassLoader無法載入類,則contextClassLoader試圖自己來載入 

Java中一共有四個類載入器(類載入器,是程式要用到某個類的時候,要用類載入器載入記憶體)。 
    這四個類載入器分別為:Bootstrap ClassLoader、Extension ClassLoader、AppClassLoader 
和URLClassLoader,其中AppClassLoader在很多地方被叫做System ClassLoader,Bootstrap ClassLoader是在JVM開始執行的時候載入java的核心類,是用C++編寫的,它用來載入核心類庫,在JVM原始碼中這樣寫道:

static const char classpathFormat[] = 
"%/lib/rt.jar:" 
"%/lib/i18n.jar:" 
"%/lib/sunrsasign.jar:" 
"%/lib/jsse.jar:" 
"%/lib/jce.jar:" 
"%/lib/charsets.jar:" 
"%/classes"; 

Extension ClassLoader是用來載入擴充套件類,即/lib/ext中的類。 
AppClassLoader用來載入Classpath的類,是和我們關係最密切的類。 
URLClassLoader用來載入網路上遠端的類 

1. 預先載入與依需求載入 

Java 執行環境為了優化系統,提高程式的執行速度,在 JRE 執行的開始會將 Java 執行所需要的基本類採用預先載入( pre-loading )的方法全部載入要記憶體當中,因為這些單元在 Java 程式執行的過程當中經常要使用的,主要包括 JRE 的 rt.jar 檔案裡面所有的 .class 檔案。 

當 java.exe 虛擬機器開始執行以後,它會找到安裝在機器上的 JRE 環境,然後把控制權交給 JRE , JRE 的類載入器會將 lib 目錄下的 rt.jar 基礎類別檔案庫載入進記憶體,這些檔案是 Java 程式執行所必須的,所以系統在開始就將這些檔案載入,避免以後的多次 IO 操作,從而提高程式執行效率。 

相對於預先載入,我們在程式中需要使用自己定義的類的時候就要使用依需求載入方法( load-on-demand ),就是在 Java 程式需要用到的時候再載入,以減少記憶體的消耗,因為 Java 語言的設計初衷就是面向嵌入式領域的。

2. 隱式載入和顯示載入 

Java 的載入方式分為隱式載入( implicit )和顯示載入( explicit )。所謂隱式載入就是我們在程式中用 new 關鍵字來定義一個例項變數, JRE 在執行到 new 關鍵字的時候就會把對應的例項類載入進入記憶體。隱式載入的方法很常見,用的也很多, JRE 系統在後臺自動的幫助使用者載入,減少了使用者的工作量,也增加了系統的安全性和程式的可讀性。

Class c = Class.forName("TestClass");   
  
 TestClass object = (TestClass)c.newInstance();   
  
object.method();   

通過 Class 類的 forName (String s) 方法把自定義類 TestClass 載入進來,並通過 newInstance ()方法把例項初始化。事實上 Class 類還很多的功能,這裡就不細講了,有興趣的可以參考 JDK 文件。 

Class 的 forName() 方法還有另外一種形式: Class forName(String s, boolean flag, ClassLoader classloader) , s 表示需要載入類的名稱, flag 表示在呼叫該函式載入類的時候是否初始化靜態區, classloader 表示載入該類所需的載入器。 

forName (String s) 是預設通過 ClassLoader.getCallerClassLoader() 呼叫類載入器的,但是該方法是私有方法,我們無法呼叫,如果我們想使用 Class forName(String s, boolean flag, ClassLoader classloader) 來載入類的話,就必須要指定類載入器,可以通過如下的方式來實現: 
 

Test test = new Test();//Test 類為自定義的一個測試類; 

ClassLoader cl = test. getClass().getClassLoader(); // 獲取 test 的類裝載器;                        

Class c = Class.forName("TestClass", true, cl); 

因為一個類要載入就必需要有載入器,這裡我們是通過獲取載入 Test 類的載入器 cl 當作載入 TestClass 的類載入器來實現載入的。 
3. 自定義類載入機制 

之前我們都是呼叫系統的類載入器來實現載入的,其實我們是可以自己定義類載入器的。利用 Java 提供的 java.net.URLClassLoader 類就可以實現。下面我們看一段範例:

 try{ 
      URL url = new URL("file:/d:/test/lib/");
      URLClassLoader urlCL = new URLClassLoader(new URL[]{url}); 
      Class c = urlCL.loadClass("TestClassA"); 
      TestClassA object = (TestClassA)c.newInstance(); 
      object.method(); 
  }catch(Exception e){ 
       e.printStackTrace(); 
  } 

我們通過自定義的類載入器實現了 TestClassA 類的載入並呼叫 method ()方法。分析一下這個程式:首先定義 URL 指定類載入器從何處載入類, URL 可以指向網際網路上的任何位置,也可以指向我們計算機裡的檔案系統 ( 包含 JAR 檔案 ) 。上述範例當中我們從 file:/d:/test/lib/ 處尋找類;然後定義 URLClassLoader 來載入所需的類,最後即可使用該例項了。 

4. 類載入器的階層體系 

 Java 的類載入器的工作原理: 

當執行 java ***.class 的時候, java.exe 會幫助我們找到 JRE ,接著找到位於 JRE 內部的 jvm.dll ,這才是真正的 Java 虛擬機器器 , 最後載入動態庫,啟用 Java 虛擬機器器。虛擬機器器啟用以後,會先做一些初始化的動作,比如說讀取系統引數等。一旦初始化動作完成之後,就會產生第一個類載入器―― Bootstrap Loader , Bootstrap Loader 是由 C++ 所撰寫而成,這個 Bootstrap Loader 所做的初始工作中,除了一些基本的初始化動作之外,最重要的就是載入 Launcher.java 之中的 ExtClassLoader ,並設定其 Parent 為 null ,代表其父載入器為 BootstrapLoader 。然後 Bootstrap Loader 再要求載入 Launcher.java 之中的 AppClassLoader ,並設定其 Parent 為之前產生的 ExtClassLoader 實體。這兩個載入器都是以靜態類的形式存在的。這裡要請大家注意的是, Launcher$ExtClassLoader.class 與 Launcher$AppClassLoader.class 都是由 Bootstrap Loader 所載入,所以 Parent 和由哪個類載入器載入沒有關係。 
這三個載入器就構成我們的 Java 類載入體系。他們分別從以下的路徑尋找程式所需要的類: 

BootstrapLoader : sun.boot.class.path 

ExtClassLoader:      java.ext.dirs 

AppClassLoader:      java.class.path 

這三個系統參量可以通過 System.getProperty() 函式得到具體對應的路徑。