1. 程式人生 > >深入探討Java的類載入機制

深入探討Java的類載入機制

 Java 語言是一種具有動態性的解釋型程式語言,當指定程式執行的時候, Java 虛擬機器就將編譯生成的 . class 檔案按照需求和一定的規則載入進記憶體,並組織成為一個完整的 Java 應用程式。 Java 語言把每個單獨的類 Class 和介面 Implements 編譯成單獨的一個 . class 檔案,這些檔案對於 Java 執行環境來說就是一個個可以動態載入的單元。正是因為 Java 的這種特性,我們可以在不重新編譯其它程式碼的情況下,只編譯需要修改的單元,並把修改檔案編譯後的 . class 檔案放到 Java 的路徑當中, 等到下次該 Java 虛擬機器器重新啟用時,這個邏輯上的 Java 應用程式就會因為載入了新修改的 .class 檔案,自己的功能也做了更新,這就是 Java 的動態性。 

    下面用一個簡單的例子讓大家對 Java 的動態載入有一個基本的認識: 

class TestClassA{ 
public void method(){ 
  System.out.println("Loading ClassA"); 



public class ClassLoaderTest { 
public static void main(String args[]){ 
  TestClassA testClassA = new TestClassA(); 
  testClassA.method(); 



編譯後輸入命令: java -verbose:class ClassLoaderTest ,執行檔案。 


從執行結果我們可以看到, JRE ( JavaRuntime Environment )首先載入 ClassLoaderTest 檔案,然後再載入 TestClassA 檔案,從而實現了動態載入。 

    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 語言的設計初衷就是面向嵌入式領域的。 

    在這裡還有一點需要說明的是, JRE 的依需求載入究竟是在什麼時候把類載入進入內部的呢? 

    我們在定義一個類例項的時候,比如 TestClassA testClassA ,這個時候 testClassA 的值為 null ,也就是說還沒有初始化,沒有呼叫 TestClassA 的建構函式,只有當執行 testClassA = new TestClassA() 以後, JRE 才正真把 TestClassA 載入進來。 
2. 隱式載入和顯示載入 

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

    相對於隱式載入的就是我們不經常用到的顯示載入。所謂顯示載入就是有程式設計師自己寫程式把需要的類載入到記憶體當中,下面我們看一段程式: 

class TestClass{ 
public void method(){ 
  System.out.println("TestClass-method"); 



public class CLTest { 
public static void main(String args[]) { 
  try{ 
  Class c = Class.forName("TestClass"); 
  TestClass object = (TestClass)c.newInstance(); 
  object.method(); 
  }catch(Exception e){ 
  e.printStackTrace(); 
  } 



    我們通過 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() 函式得到具體對應的路徑。大家可以自己程式設計實現檢視具體的路徑。