首先聲明霹粥,我是因為看了ImportNew網(wǎng)站上的幾篇關(guān)于類加載器的文章之后兼都,才萌生了寫這篇文章的想法旨涝。所以在寫這篇文章時漂羊,參考了該網(wǎng)站上的幾篇相關(guān)的文章驾锰,但是加入了很多自己的理解,絕對不是抄襲走越。
我們知道椭豫,java是一種動態(tài)語言。那么怎樣理解這個“動態(tài)”呢旨指?或者說一門語言具備了什么特性赏酥,才能稱之為動態(tài)語言呢?對于java谆构,我是這樣理解的裸扶。
我們都知道JVM(java虛擬機)執(zhí)行的不是本地機器碼指令,而是執(zhí)行一種稱之為字節(jié)碼的指令(存在于class文件中)搬素。這就要求虛擬機在真正執(zhí)行字節(jié)碼之前呵晨,先把相關(guān)的class文件加載到內(nèi)存中。虛擬機不是一次性加載所有需要的class文件熬尺,因為它在執(zhí)行的時候根本不會知道以后會用到哪些class文件摸屠。它是每用到一個類,就會在運行時“動態(tài)地”加載和這個類相關(guān)的class文件粱哼。這就是java被稱之為動態(tài)性語言的根本原因季二。除了動態(tài)加載類之外,還會動態(tài)的初始化類皂吮,對類進行動態(tài)鏈接戒傻。動態(tài)初始化和動態(tài)鏈接放在其他文章中進行介紹。本文中只關(guān)心類的加載蜂筹。
在JVM中負(fù)責(zé)對類進行加載的正是本文要介紹的類加載器(ClassLoader)需纳,所以,類加載器是JVM不可或缺的重要組件艺挪。
java中(指的是javase)有三種類加載器不翩。每個類加載器在創(chuàng)建的時候已經(jīng)指定他們對應(yīng)的目錄, 也就是說每個類加載器去哪里加載類是確定的兵扬,我認(rèn)為在ClassLoader類中應(yīng)該會有g(shù)etTargetPath()之類的方法, 得到他們對應(yīng)的路徑,找了找jdk的文檔,發(fā)現(xiàn)是沒有的口蝠。以下是這三種類加載器和他們對應(yīng)的路徑:
* AppClassLoader ?-- ? 加載classpath指定的路徑中的類
* ExtClassLoader ? -- ? 加載jre/lib/ext目錄下或者java.ext.dirs系統(tǒng)屬性定義的目錄下的類
* BootStrap ? ? ? ? ? -- ? 加載JRE/lib/rt.jar中的類
那么類加載器是如何工作的呢器钟?可以參看jdk中ClassLoader類的源碼。這個類的實現(xiàn)使用了模板方法模式妙蔗,首先是loadClass方法來加載類傲霸,loadClass方法又調(diào)用了findClass方法,該方法讀取并返回類文件的數(shù)據(jù)眉反,findClass方法返回后昙啄,loadClass方法繼續(xù)調(diào)用defineClass方法,將返回的數(shù)據(jù)加工成虛擬機運行時可識別的類型信息寸五。所以梳凛,我們?nèi)绻_發(fā)自己的類加載器,只需要繼承jdk中的ClassLoader類梳杏,并覆蓋findClass方法就可以了韧拒,剩下的而工作,父類會完成十性。其他java平臺有的根據(jù)自己的需求叛溢,實現(xiàn)了自己特定的類加載器,例如javaee平臺中的tomcat服務(wù)器烁试,android平臺中的dalvik虛擬機也定義了自己的類加載器雇初。
虛擬機加載類有兩種方式,一種方式就是上面提到的ClassLoader.loadClass()方法减响,另一種是使用反射API,Class.forName()方法郭怪,其實Class.forName()方法內(nèi)部也是使用的ClassLoader支示。Class類中forName方法的實現(xiàn)如下:
[java]view plaincopy
publicstaticClass?forName(String?name,booleaninitialize,
ClassLoader?loader)
throwsClassNotFoundException
{
if(loader?==null)?{
SecurityManager?sm?=?System.getSecurityManager();
if(sm?!=null)?{
ClassLoader?ccl?=?ClassLoader.getCallerClassLoader();
if(ccl?!=null)?{
sm.checkPermission(
SecurityConstants.GET_CLASSLOADER_PERMISSION);
}
}
}
returnforName0(name,?initialize,?loader);
}
/**?Called?after?security?checks?have?been?made.?*/
privatestaticnativeClass?forName0(String?name,booleaninitialize,
ClassLoader?loader)
throwsClassNotFoundException;
類加載器有三個特性,分別為委派鄙才,可見性和單一性颂鸿,其他文章上對這三個特性的介紹如下:
* 委托機制是指將加載一個類的請求交給父類加載器,如果這個父類加載器不能夠找到或者加載這個類攒庵,那么再加載它嘴纺。
* 可見性的原理是子類的加載器可以看見所有的父類加載器加載的類,而父類加載器看不到子類加載器加載的類浓冒。
* 單一性原理是指僅加載一個類一次栽渴,這是由委托機制確保子類加載器不會再次加載父類加載器加載過的類。
其中稳懒,委派機制是基礎(chǔ)闲擦,在其他資料中也把這種機制叫做類加載器的雙親委派模型,其實說的是同一個意思∈洌可加性和單一性是依賴于委派機制的纯路。
以下代碼測試類加載器的委派機制:
[java]view plaincopy
ClassLoader?appClassLoader?=?ClassLoaderTest.class.getClassLoader();
System.out.println(appClassLoader);//sun.misc.Launcher$AppClassLoader@19821f
ClassLoader?extClassLoader?=?appClassLoader.getParent();
System.out.println(extClassLoader);//sun.misc.Launcher$ExtClassLoader@addbf1
//AppClassLoader的父加載器是ExtClassLoader
System.out.println(extClassLoader.getParent());//null
//ExtClassLoader的父加載器是null,?也就是BootStrap,這是由c語言實現(xiàn)的
由打印結(jié)果可知,加載我們自己編寫的類的加載器是AppClassLoader寞忿,AppClassLoader的父加載器是ExtClassLoader驰唬,在而ExtClassLoader的父加載器返回結(jié)果為null,這說明他的附加載器是BootStrap腔彰,這個加載器是和虛擬機緊密聯(lián)系在一起的定嗓,在虛擬機啟動時,就會加載jdk中的類萍桌。它是由C實現(xiàn)的宵溅,沒有對應(yīng)的java對象,所以返回null上炎。但是在邏輯上恃逻,BootStrap仍是ExtClassLoader的父加載器。也就是說每當(dāng)ExtClassLoader加載一個類時藕施,總會委托給BootStrap加載寇损。
在java中,還存在兩個概念裳食,分別是系統(tǒng)類加載器和線程上下文類加載器矛市。
其實系統(tǒng)類加載器就是AppClassLoader應(yīng)用程序類加載器,它兩個值得是同一個加載器,以下代碼可以驗證:
[java]view plaincopy
ClassLoader?appClassLoader?=?ClassLoaderTest.class.getClassLoader();
System.out.println(appClassLoader);//sun.misc.Launcher$AppClassLoader@19821f
ClassLoader?sysClassLoader?=?ClassLoader.getSystemClassLoader();
System.out.println(sysClassLoader);//sun.misc.Launcher$AppClassLoader@19821f
//由上面的驗證可知,?應(yīng)用程序類加載器和系統(tǒng)類加載器是相同的,?因為地址是一樣的
這兩個類加載器對應(yīng)的輸出胳泉,不僅類名相同爹脾,連對象的哈希值都是一樣的,這充分說明系統(tǒng)類加載器和應(yīng)用程序類加載器不僅是同一個類找田,更是同一個類的同一個對象。
每個線程都會有一個上下文類加載器,由于在線程執(zhí)行時加載用到的類,默認(rèn)情況下是父線程的上下文類加載器, 也就是AppClassLoader着憨。
[java]view plaincopy
newThread(newRunnable()?{
@Override
publicvoidrun()?{
ClassLoader?threadcontextClassLosder?=?Thread.currentThread().getContextClassLoader();
System.out.println(threadcontextClassLosder);//sun.misc.Launcher$AppClassLoader@19821f
}
}).start();
這個子線程在執(zhí)行時打印的信息為sun.misc.Launcher$AppClassLoader@19821f墩衙,可以看到和主線程中的AppClassLoader是同一個對象(哈希值相同)。
也可以為線程設(shè)置特定的類加載器甲抖,這樣的話漆改,線程在執(zhí)行時就會使用這個特定的類加載器來加載使用到的類。如下代碼:
[java]view plaincopy
Thread?th?=newThread(newRunnable()?{
@Override
publicvoidrun()?{
ClassLoader?threadcontextClassLosder?=?Thread.currentThread().getContextClassLoader();
System.out.println(threadcontextClassLosder);//jg.zhang.java.testclassloader.ClassLoaderTest$3@1b67f74
}
});
th.setContextClassLoader(newClassLoader()?{});
th.start();
在線程運行之前准谚,為它設(shè)置了一個匿名內(nèi)部類的類加載器對象挫剑,線程運行時,輸出的信息為:jg.zhang.java.testclassloader.ClassLoaderTest$3@1b67f74氛魁,也就是我們設(shè)置的那個類加載器對象暮顺。
下面驗證類加載器的可見性厅篓,也就是?子類的加載器可以看見所有的父類加載器加載的類,而父類加載器看不到子類加載器加載的類捶码。
以下代碼使用父加載器ExtClassLoader加載子加載器AppClassLoader路徑下的類羽氮,由輸出可知,是不可能實現(xiàn)的惫恼。
[java]view plaincopy
try{
Class.forName("jg.zhang.java.testConcurrent.Person",true,
ClassLoaderTest.class.getClassLoader().getParent());
System.out.println("1?--?類被加載");
}catch(ClassNotFoundException?e)?{
//e.printStackTrace();
System.out.println("1?--?未找到類");
}
輸出為 :1 -- 未找到類 档押。說明拋出了ClassNotFoundException異常。原因是讓ExtClassLoader加載 jg.zhang.java.testConcurrent.Person這個類因為這個類不在jre/lib/ext目錄下或者java.ext.dirs系統(tǒng)屬性定義的目錄下祈纯,所以拋出ClassNotFoundException令宿。所以父加載器不能加載應(yīng)該被子加載器加載的類。也就是說這個類在父加載器中不可見腕窥。這種機制依賴于委派機制粒没。
下面代碼使用子加載器AppClassLoader 加載父加載器BootStrap中的類,這是可以實現(xiàn)的簇爆。
[java]view plaincopy
try{
Class.forName("java.lang.String",true,
ClassLoaderTest.class.getClassLoader());
System.out.println("2?--?類被加載");
}catch(ClassNotFoundException?e)?{
//e.printStackTrace();
System.out.println("2?--?未找到類");
}
輸出為:2 -- 類被加載癞松。說明成功加載了String類。是因為在指定由AppClassLoader加載String類時入蛆,由AppClassLoader一直委派到BootStrap加載响蓉。雖然是由子加載器的父加載器加載的,但是也可以說哨毁,父加載器加載的類對于子加載器來說是可見的枫甲。這同樣依賴于委派機制。其實在虛擬機啟動初期扼褪,java.lang.String已經(jīng)被BootStrap預(yù)加載了想幻,這時再次加載,虛擬機發(fā)現(xiàn)已經(jīng)加載迎捺,不會再重復(fù)加載举畸。這同時也證明了類加載器的單一性。
到此為止凳枝,類加載器的知識就全部講完了。以下是整個測試代碼:
[java]view plaincopy
packagejg.zhang.java.testclassloader;
/**
*?參考ImportNew上的一篇文章<<類加載器的工作原理>>,
*?文章地址:http://www.importnew.com/6581.html
*
*?Java類加載器基于三個機制:委托跋核、可見性和單一性岖瑰。
*?委托機制是指將加載一個類的請求交給父類加載器,如果這個父類加載器不能夠找到或者加載這個類砂代,那么再加載它蹋订。
*?可見性的原理是子類的加載器可以看見所有的父類加載器加載的類,而父類加載器看不到子類加載器加載的類刻伊。
*?單一性原理是指僅加載一個類一次露戒,這是由委托機制確保子類加載器不會再次加載父類加載器加載過的類椒功。
*
*?三種類加載器:?每個類加載器在創(chuàng)建的時候已經(jīng)指定他們對應(yīng)的目錄,?也就是說每個類加載器去哪里加載類是確定的
*?我認(rèn)為在ClassLoader類中應(yīng)該會有g(shù)etTargetPath()之類的方法,?得到他們對應(yīng)的路徑,找了找jdk的文檔,發(fā)現(xiàn)是沒有的.
*?AppClassLoader??--???加載classpath指定的路徑中的類
*?ExtClassLoader??--???加載jre/lib/ext目錄下或者java.ext.dirs系統(tǒng)屬性定義的目錄下的類
*?BootStrap???????--???加載JRE/lib/rt.jar中的類
*
*
*
*?@author?zhangjg
*
*/
publicclassClassLoaderTest?{
publicstaticvoidmain(String[]?args)?{
test1();
test2();
test3();
}
/**
*?驗證線程上下文類加載器
*/
privatestaticvoidtest3()?{
/**
*?1?每個線程都會有一個上下文類加載器,由于在線程執(zhí)行時加載用到的類,默認(rèn)情況下是父線程
*?的上下文類加載器,?也就是AppClassLoader
*/
newThread(newRunnable()?{
@Override
publicvoidrun()?{
ClassLoader?threadcontextClassLosder?=?Thread.currentThread().getContextClassLoader();
System.out.println(threadcontextClassLosder);//sun.misc.Launcher$AppClassLoader@19821f
}
}).start();
/**
*?2?也可以給創(chuàng)建的線程設(shè)定特定的上下文類加載器
*/
Thread?th?=newThread(newRunnable()?{
@Override
publicvoidrun()?{
ClassLoader?threadcontextClassLosder?=?Thread.currentThread().getContextClassLoader();
System.out.println(threadcontextClassLosder);//jg.zhang.java.testclassloader.ClassLoaderTest$3@1b67f74
}
});
th.setContextClassLoader(newClassLoader()?{});
th.start();
}
/**
*?測試可見性,可見性依賴于委托機制
*/
privatestaticvoidtest2()?{
/**
*?1?讓ExtClassLoader加載?jg.zhang.java.testConcurrent.Person這個類
*?因為這個類不在jre/lib/ext目錄下或者java.ext.dirs系統(tǒng)屬性定義的目錄下
*?所以拋出ClassNotFoundException
*
*?所以父加載器不能加載應(yīng)該被子加載器加載的類,這個類在父加載器中不可見
*?這種機制依賴于委派機制
*/
try{
Class.forName("jg.zhang.java.testConcurrent.Person",true,
ClassLoaderTest.class.getClassLoader().getParent());
System.out.println("1?--?類被加載");
}catch(ClassNotFoundException?e)?{
//e.printStackTrace();
System.out.println("1?--?未找到類");
}
/**
*?2?讓AppClassLoader加載java.lang.String類
*?沒有拋出異常,說明類被正常加載了
*?雖然是由AppClassLoader一直委派到BootStrap而加載的
*?所以可以說,父加載器加載的類對于子加載器來說是可見的,這同樣依賴于委派機制
*
*?其實在虛擬機啟動初期,java.lang.String已經(jīng)被BootStrap預(yù)加載了
*?這時再次加載,虛擬機發(fā)現(xiàn)已經(jīng)加載,不會再重復(fù)加載
*/
try{
Class.forName("java.lang.String",true,
ClassLoaderTest.class.getClassLoader());
System.out.println("2?--?類被加載");
}catch(ClassNotFoundException?e)?{
//e.printStackTrace();
System.out.println("2?--?未找到類");
}
}
/**
*?驗證三種類加載器的父子關(guān)系
*/
privatestaticvoidtest1()?{
ClassLoader?appClassLoader?=?ClassLoaderTest.class.getClassLoader();
System.out.println(appClassLoader);//sun.misc.Launcher$AppClassLoader@19821f
ClassLoader?sysClassLoader?=?ClassLoader.getSystemClassLoader();
System.out.println(sysClassLoader);//sun.misc.Launcher$AppClassLoader@19821f
//由上面的驗證可知,?應(yīng)用程序類加載器和系統(tǒng)類加載器是相同的,?因為地址是一樣的
ClassLoader?extClassLoader?=?appClassLoader.getParent();
System.out.println(extClassLoader);//sun.misc.Launcher$ExtClassLoader@addbf1
//AppClassLoader的父加載器是ExtClassLoader
System.out.println(extClassLoader.getParent());//null
//ExtClassLoader的父加載器是null,?也就是BootStrap,這是由c語言實現(xiàn)的
}
}