1. 程式人生 > >對ClassLoader的認識與總結

對ClassLoader的認識與總結

類載入器用來把類載入到Java虛擬機器中。從JDK 1.2版本開始,類的載入過程採用父親委託機制,這種委託機制能更好地保證Java平臺的安全性。

一、  ClassLoader的概念

ClassLoader是一個抽象類,我們用它的例項物件來裝載類,它負責將Java位元組碼裝載到JVM 中,並使其成為JVM 一部分。JVM 的類動態裝載技術能夠在執行時刻動態地載入或者替換系統的某些功能模組,而不影響系統其他功能模組的正常執行。

二、  Java虛擬機器自帶的幾種載入器簡介

1.Bootstrap ClassLoader/啟動類載入器 :主要負責jdk_home/lib目錄下的核心api 或-Xbootclasspath 選項指定的jar包裝入工作。

2.Extension ClassLoader/擴充套件類載入器 :主要負責jdk_home/lib/ext目錄下的jar包或-Djava.ext.dirs 指定目錄下的jar包裝入工作。

3.System ClassLoader/系統類載入器 :主要負責java-classpath/-Djava.class.path所指的目錄下的類與jar包裝入工作。

4.User Custom ClassLoader/使用者自定義類載入器(java.lang.ClassLoader的子類) :在程式執行期間,通過java.lang.ClassLoader的子類動態載入class檔案,體現java動態實時類裝入特性。

其中BootstrapClassLoader是JVM級別的,由C++撰寫;ExtensionClassLoader、AppClassLoader都是java類,都繼承自URLClassLoader超類。BootstrapClassLoader由JVM啟動,然後初始化sun.misc.Launcher,sun.misc.Launcher初始化ExtensionClassLoader、AppClassLoader。

Bootstrap ClassLoader、ExtensionClassLoader、AppClassLoader三者的關係如下:

Bootstrap ClassLoader是ExtensionClassLoader的parent,ExtensionClassLoader是AppClassLoader的parent。但是這並不是繼承關係,只是語義上的定義,基本上,每一個ClassLoader實現,都有一個ParentClassLoader。可以通過ClassLoader的getParent方法得到當前ClassLoader的parent。BootstrapClassLoader比較特殊,因為它不是javaclass所以ExtensionClassLoader的getParent方法返回的是NULL。

類載入器的特性有兩點:

1.每個ClassLoader都維護了一份自己的名稱空間,同一個名稱空間裡不能出現兩個同名的類。

2.為了實現java安全沙箱模型頂層的類載入器安全機制,java預設採用了" 雙親委派的載入鏈" 結構。

 

三、  Java類載入器classLoader的工作機制

類載入器就是尋找類或介面位元組碼檔案進行解析並構造JVM內部物件表示的元件。在Java中,類轉載器把一個類裝入JVM中,需要經過以下步驟:

 1.裝載:查詢和匯入Class檔案;

 2.連結:執行校驗、準備和解析步驟,其中解析步驟是可以選擇的:

 a)校驗:檢查載入Class檔案資料的正確性;

 b)準備:給類的靜態變數分配儲存空間;

 c)解析:將符號引用變成直接引用;

 3.初始化:對類的靜態變數、靜態程式碼塊進行初始化工作。

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

四、  ClassLoader的工作原理分析

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 系統在後臺自動的幫助使用者載入,減少了使用者的工作量,也增加了系統的安全性和程式的可讀性。 

Java程式碼  

1.  Class c = Class.forName("TestClass");   

2.    

3.   TestClass object = (TestClass)c.newInstance();   

4.    

5.  object.method();   

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

Class 的 forName() 方法還有另外一種形式: Class forName(String s, boolean flag, ClassLoaderclassloader) , 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 的類載入器來實現載入的。 

2.  自定義類載入機制 

之前我們都是呼叫系統的類載入器來實現載入的,其實我們是可以自己定義類載入器的。利用 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 來載入所需的類,最後即可使用該例項了。 

3.  類載入器的階層體系 

當執行 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() 函式得到具體對應的路徑。 

五、  重要問題總結

1.為什麼要使用這種雙親委託模式呢?

因為這樣可以避免重複載入,當父親已經載入了該類的時候,就沒有必要子ClassLoader再載入一次。考慮到安全因素,我們試想一下,如果不使用這種委託模式,那我們就可以隨時使用自定義的String來動態替代java核心api中定義型別,這樣會存在非常大的安全隱患,而雙親委託的方式,就可以避免這種情況,因為String已經在啟動時被載入,所以使用者自定義類是無法載入一個自定義的ClassLoader。

2.java動態載入class的兩種方式是?

implicit隱式,即利用例項化才載入的特性來動態載入class

explicit顯式方式,又分兩種方式:①java.lang.Class的forName()方法②java.lang.ClassLoader的loadClass()方法

3.解釋用Class.forName載入類

Class.forName使用的是被呼叫者的類載入器來載入類的。
這種特性, 證明了java類載入器中的名稱空間是唯一的, 不會相互干擾。
即在一般情況下, 保證同一個類中所關聯的其他類都是由當前類的類載入器所載入的。

view sourceprint?

1

public static Class forName(String className) 

2

     throws ClassNotFoundException { 

 

3

     return forName0(className, true , ClassLoader.getCallerClassLoader()); 

4

}  

 

5

   

6

/** Called after security checks have been made. */ 

 

7

private static native Class forName0(String name, boolean initialize, 

8

ClassLoader loader) 

 

9

     throws ClassNotFoundException;

上面中 ClassLoader.getCallerClassLoader 就是得到呼叫當前forName方法的類的類載入器

4.static塊在什麼時候執行?

當呼叫forName(String)載入class時執行,如果呼叫ClassLoader.loadClass並不會執行.forName(String,false,ClassLoader)時也不會執行.

如果載入Class時沒有執行static塊則在第一次例項化時執行.比如new,Class.newInstance()操作static塊僅執行一次

5.各個java類由哪些classLoader載入?

ava類可以通過例項.getClass.getClassLoader()得知介面由AppClassLoader(SystemClassLoader,可以由ClassLoader.getSystemClassLoader()獲得例項)載入;ClassLoader類由bootstrap loader載入

6.NoClassDefFoundError和ClassNotFoundException

NoClassDefFoundError:當java原始檔已編譯成.class檔案,但是ClassLoader在執行期間在其搜尋路徑load某個類時,沒有找到.class檔案則報這個錯

ClassNotFoundException:試圖通過一個String變數來建立一個Class類時不成功則丟擲這個異常