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語言實現的
}
}