當前位置:才華齋>計算機>java語言>

Java中的類載入器

java語言 閱讀(2.71W)

Java中的類載入器

Java中的類載入器

Java是一門面向物件程式語言,那麼大家知道 Java中的類載入器是什麼呢?下面一起來看看!

從java的動態性到類載入機制

我們知道,Java是一種動態語言。那麼怎樣理解這個“動態”呢?或者說一門語言具備了什麼特性,才能稱之為動態語言呢?對於java,我是這樣理解的。

我們都知道JVM(java虛擬機器)執行的不是本地機器碼指令,而是執行一種稱之為位元組碼的指令(存在於class檔案中)。這就要求虛擬機器在真正執行位元組碼之前,先把相關的class檔案載入到記憶體中。虛擬機器不是一次性載入所有需要的class檔案,因為它在執行的時候根本不會知道以後會用到哪些class檔案。它是每用到一個類,就會在執行時“動態地”載入和這個類相關的class檔案。這就是java被稱之為動態性語言的根本原因。除了動態載入類之外,還會動態的初始化類,對類進行動態連結。動態初始化和動態連結放在其他文章中進行介紹。本文中只關心類的載入。

在JVM中負責對類進行載入的正是本文要介紹的類載入器(ClassLoader),所以,類載入器是JVM不可或缺的重要元件。

java中的類載入器及類載入器工作原理

java中(指的是javase)有三種類載入器。每個類載入器在建立的時候已經指定他們對應的目錄, 也就是說每個類載入器去哪裡載入類是確定的,我認為在ClassLoader類中應該會有getTargetPath()之類的方法, 得到他們對應的路徑,找了找jdk的文件,發現是沒有的。以下是這三種類載入器和他們對應的路徑:

* AppClassLoader -- 載入classpath指定的路徑中的類

* ExtClassLoader -- 載入jre/lib/ext目錄下或者系統屬性定義的目錄下的類

* BootStrap -- 載入JRE/lib/中的類

那麼類載入器是如何工作的呢?可以參看jdk中ClassLoader類的原始碼。這個類的實現使用了模板方法模式,首先是loadClass方法來載入類,loadClass方法又呼叫了findClass方法,該方法讀取並返回類檔案的資料,findClass方法返回後,loadClass方法繼續呼叫defineClass方法,將返回的資料加工成虛擬機器執行時可識別的型別資訊。所以,我們如果開發自己的類載入器,只需要繼承jdk中的ClassLoader類,並覆蓋findClass方法就可以了,剩下的而工作,父類會完成。其他java平臺有的根據自己的需求,實現了自己特定的類載入器,例如javaee平臺中的tomcat服務器,Android平臺中的dalvik虛擬機器也定義了自己的類載入器。

虛擬機器載入類有兩種方式,一種方式就是上面提到的Class()方法,另一種是使用反射API,ame()方法,其實ame()方法內部也是使用的ClassLoader。Class類中forName方法的實現如下:

[java] view plain copypublic static Class forName(String name, boolean initialize,

ClassLoader loader)

throws ClassNotFoundException

{

if (loader == null) {

SecurityManager sm = ecurityManager();

if (sm != null) {

ClassLoader ccl = allerClassLoader();

if (ccl != null) {

kPermission(

_CLASSLOADER_PERMISSION);

}

}

}

return forName0(name, initialize, loader);

}

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

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

ClassLoader loader)

throws ClassNotFoundException;

 類載入器的三個特性

類載入器有三個特性,分別為委派,可見性和單一性,其他文章上對這三個特性的介紹如下:

* 委託機制是指將載入一個類的請求交給父類載入器,如果這個父類載入器不能夠找到或者載入這個類,那麼再載入它。

* 可見性的原理是子類的載入器可以看見所有的父類載入器載入的類,而父類載入器看不到子類載入器載入的類。

* 單一性原理是指僅載入一個類一次,這是由委託機制確保子類載入器不會再次載入父類載入器載入過的類。

其中,委派機制是基礎,在其他資料中也把這種機制叫做類載入器的雙親委派模型,其實說的是同一個意思。可加性和單一性是依賴於委派機制的。

以下程式碼測試類載入器的委派機制:

[java] view plain copyClassLoader appClassLoader = lassLoader();

tln(appClassLoader); //cher$AppClassLoader@19821f

ClassLoader extClassLoader = arent();

tln(extClassLoader); //cher$ExtClassLoader@addbf1

//AppClassLoader的父載入器是ExtClassLoader

tln(arent()); //null

//ExtClassLoader的父載入器是null, 也就是BootStrap,這是由c語言實現的

由列印結果可知,載入我們自己編寫的類的載入器是AppClassLoader,AppClassLoader的父載入器是ExtClassLoader,在而ExtClassLoader的父載入器返回結果為null,這說明他的附載入器是BootStrap,這個載入器是和虛擬機器緊密聯絡在一起的,在虛擬機器啟動時,就會載入jdk中的類。它是由C實現的,沒有對應的java物件,所以返回null。但是在邏輯上,BootStrap仍是ExtClassLoader的父載入器。也就是說每當ExtClassLoader載入一個類時,總會委託給BootStrap載入。

系統類載入器和執行緒上下文類載入器

在java中,還存在兩個概念,分別是系統類載入器和執行緒上下文類載入器。

其實系統類載入器就是AppClassLoader應用程式類載入器,它兩個值得是同一個載入器,以下程式碼可以驗證:

[java] view plain copyClassLoader appClassLoader = lassLoader();

tln(appClassLoader); //cher$AppClassLoader@19821f

ClassLoader sysClassLoader = ystemClassLoader();

tln(sysClassLoader); //cher$AppClassLoader@19821f

//由上面的驗證可知, 應用程式類載入器和系統類載入器是相同的, 因為地址是一樣的

這兩個類載入器對應的輸出,不僅類名相同,連物件的雜湊值都是一樣的,這充分說明系統類載入器和應用程式類載入器不僅是同一個類,更是同一個類的同一個物件。

每個執行緒都會有一個上下文類載入器,由於線上程執行時載入用到的類,預設情況下是父執行緒的上下文類載入器, 也就是AppClassLoader。

[java] view plain copynew Thread(new Runnable() {

@Override

public void run() {

ClassLoader threadcontextClassLosder = entThread()ontextClassLoader();

tln(threadcontextClassLosder); //cher$AppClassLoader@19821f

}

})t();

這個子執行緒在執行時列印的資訊為cher$AppClassLoader@19821f,可以看到和主執行緒中的AppClassLoader是同一個物件(雜湊值相同)。

也可以為執行緒設定特定的類載入器,這樣的話,執行緒在執行時就會使用這個特定的類載入器來載入使用到的類。如下程式碼:

[java] view plain copyThread th = new Thread(new Runnable() {

@Override

public void run() {

ClassLoader threadcontextClassLosder = entThread()ontextClassLoader();

tln(threadcontextClassLosder); //sLoaderTest$3@1b67f74

}

});

ontextClassLoader(new ClassLoader() {});

t();

線上程執行之前,為它設定了一個匿名內部類的類載入器物件,執行緒執行時,輸出的資訊為:sLoaderTest$3@1b67f74,也就是我們設定的那個類載入器物件。

類載入器的可見性

下面驗證類載入器的可見性,也就是 子類的載入器可以看見所有的父類載入器載入的類,而父類載入器看不到子類載入器載入的類。

以下程式碼使用父載入器ExtClassLoader載入子載入器AppClassLoader路徑下的類,由輸出可知,是不可能實現的。

[java] view plain copytry {

ame("on", true,

lassLoader()arent());

tln("1 -- 類被載入");

} catch (ClassNotFoundException e) {

//tStackTrace();

tln("1 -- 未找到類");

}

輸出為 :1 -- 未找到類 。說明丟擲了ClassNotFoundException異常。原因是讓ExtClassLoader載入 on這個類因為這個類不在jre/lib/ext目錄下或者系統屬性定義的目錄下,所以丟擲ClassNotFoundException。所以父載入器不能載入應該被子載入器載入的類。也就是說這個類在父載入器中不可見。這種機制依賴於委派機制。

下面程式碼使用子載入器AppClassLoader 載入父載入器BootStrap中的類,這是可以實現的。

[java] view plain copytry {

ame("ng", true,

lassLoader());

tln("2 -- 類被載入");

} catch (ClassNotFoundException e) {

//tStackTrace();

tln("2 -- 未找到類");

}

輸出為:2 -- 類被載入。說明成功載入了String類。是因為在指定由AppClassLoader載入String類時,由AppClassLoader一直委派到BootStrap載入。雖然是由子載入器的父載入器載入的,但是也可以說,父載入器載入的類對於子載入器來說是可見的。這同樣依賴於委派機制。其實在虛擬機器啟動初期,ng已經被BootStrap預載入了,這時再次載入,虛擬機發現已經載入,不會再重複載入。這同時也證明了類載入器的單一性。

測試程式碼

到此為止,類載入器的知識就全部講完了。以下是整個測試程式碼:

[java] view plain copypackage classloader;

* Java類載入器基於三個機制:委託、可見性和單一性。

* 委託機制是指將載入一個類的請求交給父類載入器,如果這個父類載入器不能夠找到或者載入這個類,那麼再載入它。

* 可見性的原理是子類的載入器可以看見所有的'父類載入器載入的類,而父類載入器看不到子類載入器載入的類。

* 單一性原理是指僅載入一個類一次,這是由委託機制確保子類載入器不會再次載入父類載入器載入過的類。

*

* 三種類載入器: 每個類載入器在建立的時候已經指定他們對應的目錄, 也就是說每個類載入器去哪裡載入類是確定的

* 我認為在ClassLoader類中應該會有getTargetPath()之類的方法, 得到他們對應的路徑,找了找jdk的文件,發現是沒有的.

* AppClassLoader -- 載入classpath指定的路徑中的類

* ExtClassLoader -- 載入jre/lib/ext目錄下或者系統屬性定義的目錄下的類

* BootStrap -- 載入JRE/lib/中的類

*

*

*

* @author zhangjg

*

*/

public class ClassLoaderTest {

public static void main(String[] args) {

test1();

test2();

test3();

}

/**

* 驗證執行緒上下文類載入器

*/

private static void test3() {

/**

* 1 每個執行緒都會有一個上下文類載入器,由於線上程執行時載入用到的類,預設情況下是父執行緒

* 的上下文類載入器, 也就是AppClassLoader

*/

new Thread(new Runnable() {

@Override

public void run() {

ClassLoader threadcontextClassLosder = entThread()ontextClassLoader();

tln(threadcontextClassLosder); //cher$AppClassLoader@19821f

}

})t();

/**

* 2 也可以給建立的執行緒設定特定的上下文類載入器

*/

Thread th = new Thread(new Runnable() {

@Override

public void run() {

ClassLoader threadcontextClassLosder = entThread()ontextClassLoader();

tln(threadcontextClassLosder); //sLoaderTest$3@1b67f74

}

});

ontextClassLoader(new ClassLoader() {});

t();

}

/**

* 測試可見性,可見性依賴於委託機制

*/

private static void test2() {

/**

* 1 讓ExtClassLoader載入 on這個類

* 因為這個類不在jre/lib/ext目錄下或者系統屬性定義的目錄下

* 所以丟擲ClassNotFoundException

*

* 所以父載入器不能載入應該被子載入器載入的類,這個類在父載入器中不可見

* 這種機制依賴於委派機制

*/

try {

ame("on", true,

lassLoader()arent());

tln("1 -- 類被載入");

} catch (ClassNotFoundException e) {

//tStackTrace();

tln("1 -- 未找到類");

}

/**

* 2 讓AppClassLoader載入ng類

* 沒有丟擲異常,說明類被正常載入了

* 雖然是由AppClassLoader一直委派到BootStrap而載入的

* 所以可以說,父載入器載入的類對於子載入器來說是可見的,這同樣依賴於委派機制

*

* 其實在虛擬機器啟動初期,ng已經被BootStrap預載入了

* 這時再次載入,虛擬機發現已經載入,不會再重複載入

*/

try {

ame("ng", true,

lassLoader());

tln("2 -- 類被載入");

} catch (ClassNotFoundException e) {

//tStackTrace();

tln("2 -- 未找到類");

}

}

/**

* 驗證三種類載入器的父子關係

*/

private static void test1() {

ClassLoader appClassLoader = lassLoader();

tln(appClassLoader); //cher$AppClassLoader@19821f

ClassLoader sysClassLoader = ystemClassLoader();

tln(sysClassLoader); //cher$AppClassLoader@19821f

//由上面的驗證可知, 應用程式類載入器和系統類載入器是相同的, 因為地址是一樣的

ClassLoader extClassLoader = arent();

tln(extClassLoader); //cher$ExtClassLoader@addbf1

//AppClassLoader的父載入器是ExtClassLoader

tln(arent()); //null

//ExtClassLoader的父載入器是null, 也就是BootStrap,這是由c語言實現的

}

}