1. 程式人生 > >java中類的載入機制

java中類的載入機制

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

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

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

圖( 2 )我們可以看到多個基礎類被載入, java.lang.Object,java.io.Serializable 等等。

圖( 2 )

相對於預先載入,我們在程式中需要使用自己定義的類的時候就要使用依需求載入方法( 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 和由哪個類載入器載入沒有關係。

下面的圖形可以表示三者之間的關係:

父類

父類

載入

載入

BootstrapLoader

        PARENT

AppClassLoader

PARENT

ExtClassLoader

這三個載入器就構成我們的 Java 類載入體系。他們分別從以下的路徑尋找程式所需要的類:

BootstrapLoader : sun.boot.class.path

ExtClassLoader:      java.ext.dirs

AppClassLoader:      java.class.path

這三個系統參量可以通過 System.getProperty() 函式得到具體對應的路徑。大家可以自己程式設計實現檢視具體的路徑。

實現自己的類載入器

Java程式碼 複製程式碼
  1. import java.io.IOException;   
  2. import java.net.URL;   
  3. import java.net.URLClassLoader;   
  4. import java.lang.reflect.Method;   
  5. public class Test {   
  6. static {   
  7.          System.out.println( "/n/n.......... Test class/n/n" );   
  8.      }   
  9. public static
    void main(String[] args) throws Exception{   
  10.          URL url = null ;   
  11. try {   
  12.                  url = new URL( "file:/d:/temp/" );   
  13.              } catch (Exception e) {   
  14.                  e.printStackTrace();   
  15.              }   
  16.           URLClassLoader cl = new
    URLClassLoader( new URL[]{url});   
  17.           Class clz =   cl.loadClass( "Name" );   
  18.          Method method = clz.getMethod( "sayHello5" );   
  19.          method.invoke(clz.newInstance());   
  20.      }   
  21. }  
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.lang.reflect.Method;
public class Test {
 static {

  System.out.println("/n/n.......... Test class/n/n");
 }

 public static void main(String[] args) throws Exception{
  URL url = null;
  try {
    url = new URL("file:/d:/temp/");
   } catch (Exception e) {
    e.printStackTrace();
   }
   URLClassLoader cl = new URLClassLoader(new URL[]{url});

   Class clz =  cl.loadClass("Name");

  Method method = clz.getMethod("sayHello5");
  method.invoke(clz.newInstance());
 

 }
}


使用了反射的方法來呼叫自定義類載入器載入上來的類。

Java程式碼 複製程式碼
  1. public class Name   
  2. {   
  3. public Name(){   
  4.          System.out.println( "Name Class" );   
  5.      }   
  6. static {   
  7.          System.out.println( "This is a static area" );   
  8.      }   
  9. public void sayHello(){   
  10.          System.out.println( "Name Class Say Hello" );   
  11.      }