虛擬機(jī)設(shè)計(jì)團(tuán)隊(duì)把類加載階段中的 “通過一個(gè)類的全限定名來獲取描述此類的二進(jìn)制字節(jié)流” 這個(gè)動(dòng)作放到虛擬機(jī)外部去實(shí)現(xiàn),以便讓應(yīng)用程序自己決定如何去獲取所需要的類,實(shí)現(xiàn)這個(gè)動(dòng)作的代碼模塊稱為 “類加載器”。
一. 類與類加載器
類加載器雖然只用于實(shí)現(xiàn)類的加載動(dòng)作,但是在Java程序中起到的作用遠(yuǎn)遠(yuǎn)不限于類加載階段。對(duì)于任何一個(gè)類,都需要由加載它的類加載器本身和這個(gè)類本身來確立其在Java虛擬機(jī)中的唯一性。比較兩個(gè)類是否 相等,只有在這兩個(gè)類是由同一個(gè)類加載器加載的前提下才有意義。相等是指,代表類的Class對(duì)象的equals()、isAssignableFrom()、isInstance()方法的返回結(jié)果,以及instanceof關(guān)鍵字做對(duì)象所屬關(guān)系判定。
二. 雙親委派模型
從java虛擬機(jī)的角度來講,只存在以下兩種不同的類加載器:
- 啟動(dòng)類加載器(Bootstrap ClassLoader),這個(gè)類加載器用C++實(shí)現(xiàn),是虛擬機(jī)自身的一部分。
-
所有其他類的加載器,這些類由Java實(shí)現(xiàn),獨(dú)立于虛擬機(jī)外部,并且全都繼承自抽象類
java.lang.ClassLoader。
還可以細(xì)分為以下三種:
-
啟動(dòng)類加載器(Bootstrap ClassLoader) 此類加載器負(fù)責(zé)將存放在
<JAVA_HOME>\lib目錄中的,或者被-Xbootclasspath參數(shù)所指定的路徑中的,并且是 虛擬機(jī)識(shí)別 的(僅按照文件名識(shí)別,如rt.jar,名字不符合的類庫即使放在lib目錄中也不會(huì)被加載)類庫加載到虛擬機(jī)內(nèi)存中。啟動(dòng)類加載器無法被Java程序直接引用,用戶在編寫自定義類加載器時(shí),如果需要把加載請求委派給引導(dǎo)類加載器,直接使用null代替即可。 -
擴(kuò)展類加載器(Extension ClassLoader) 這個(gè)類加載器是由
ExtClassLoader(sun.misc.Launcher$ExtClassLoader)實(shí)現(xiàn)的。它負(fù)責(zé)將<Java_Home>/lib/ext或者被java.ext.dir系統(tǒng)變量所指定路徑中的所有類庫加載到內(nèi)存中,開發(fā)者可以直接使用擴(kuò)展類加載器。 -
應(yīng)用程序類加載器(Application ClassLoader) 這個(gè)類加載器是由
AppClassLoader(sun.misc.Launcher$AppClassLoader)實(shí)現(xiàn)的。由于這個(gè)類加載器是ClassLoader中的getSystemClassLoader()方法的返回值,因此一般稱為 系統(tǒng)類加載器。它負(fù)責(zé)加載用戶類路徑(ClassPath)上所指定的類庫,開發(fā)者可以直接使用這個(gè)類加載器,如果應(yīng)用程序中沒有自定義過自己的類加載器,一般情況下這個(gè)就是程序中 默認(rèn)的類加載器 。
類在加載器之間存在層次關(guān)系,也成為類加載器的 雙親委派模型(Parents Delgation Model),如下圖所示。該模型要求除了頂層的啟動(dòng)類加載器外,其余的類加載器都應(yīng)有自己的父類加載器,這里類加載器之間的父子關(guān)系一般通過 組合(Composition) 關(guān)系來實(shí)現(xiàn),而不是通過繼承(Inheritance)的關(guān)系實(shí)現(xiàn)。

工作過程
如果一個(gè)類加載器收到了類加載的請求,它首先不會(huì)自己去嘗試加載,而是把這個(gè)請求委派給父類加載器,每一個(gè)層次的加載器都是如此,依次遞歸,因此 所有的加載請求最終都應(yīng)該傳送到頂層的啟動(dòng)類加載器中,只有當(dāng)父加載器反饋?zhàn)约簾o法完成此加載請求(它搜索范圍中沒有找到所需類)時(shí),子加載器才會(huì)嘗試自己加載。
優(yōu)點(diǎn)
使用雙親委派模型來組織類加載器之間的關(guān)系,使得Java類隨著它的類加載器一起具備了一種 帶有優(yōu)先級(jí)的層次關(guān)系。例如類java.lang.Object,它存放再rt.jar中,無論哪個(gè)類加載器要加載這個(gè)類,最終都是委派給處于模型最頂端的啟動(dòng)類加載器進(jìn)行加載,因此Object類在程序的各種類加載器環(huán)境中都是同一個(gè)類。所以,雙親委派很好地解決了各個(gè)類加載器的 基礎(chǔ)類的統(tǒng)一 問題(越基礎(chǔ)的類由越上層的加載器進(jìn)行加載)。
同時(shí),也可以避免核心API被篡改。
缺陷
在雙親委派模型中,委派是單向的,子類加載器會(huì)委派給父類加載器,但是父類加載器無法委托給子類加載器,所以啟動(dòng)類加載器無法加載拓展類加載器加載的類,拓展類加載器也無法加載應(yīng)用程序類加載器加載的類。
實(shí)現(xiàn)
實(shí)現(xiàn)雙親委派的代碼都集中在java.lang.ClassLoader的loadClass()方法之中。如下代碼所示:
protected synchronized Class<?> loadClass(String name,boolean resolve)throws ClassNotFoundException{
//檢查請求的類是否加載過了
Class c = findLoadedClass(name);
if(c == null){
try{
if(parent != null){
c = parent.loadClass(name,false);
}else{
c = findBootstrapClassOrNull(name);
}
}catch(ClassNotFoundException e){
//如果拋出異常,說明父加載器無法完成加載請求
}
if(c == null){
//在父類無法加載的時(shí)候
//再調(diào)用本身的findClass方法來進(jìn)行類加載
c = findClass(name);
}
}
if(resolve){
resolveClass(c);
}
return c;
}
步驟為:
首先,檢查請求的類是否加載過了,如果加載過了,就不需要再加載,直接返回。
如果此類沒有加載過,那么,再判斷一下是否有父加載器;如果有父加載器,則由父加載器加載(即調(diào)用
parent.loadClass(name, false);)?;蛘呤钦{(diào)用bootstrap類加載器來加載。如果父加載器及bootstrap類加載器都沒有找到指定的類,那么調(diào)用當(dāng)前類加載器的findClass方法來完成類加載。
三. 初始類加載器和定義類加載器
在類加載的雙親委派模型下,類加載器首先會(huì)代理給它的父加載器來嘗試加載某個(gè)類,所以真正完成類的加載工作的類加載器和啟動(dòng)這個(gè)類加載過程的類加載器有可能不是同一個(gè)。真正完成類的加載工作是通過調(diào)用defineClass來實(shí)現(xiàn)的;而啟動(dòng)類的加載過程是通過調(diào)用loadClass來實(shí)現(xiàn)的。前者稱為一個(gè)類的定義加載器(defining loader),后者稱為初始加載器(initiating loader)。
JVM文檔是這樣說明的, 假設(shè)類或接口C的創(chuàng)建是因?yàn)轭惢蚪涌?code>D觸發(fā)的:
If D was defined by the bootstrap class loader, then the bootstrap class loader initiates loading of C (§5.3.1).
If D was defined by a user-defined class loader, then that same user-defined class loader initiates loading of C (§5.3.2).
也就是,如果D被啟動(dòng)類加載器定義,那么啟動(dòng)類加載器就是C的初始加載器。如果D被其他加載器定義,那么同一個(gè)加載器初始化C的加載,也就是C的初始加載器。
在 Java 虛擬機(jī)判斷兩個(gè)類是否相同的時(shí)候,使用的是類的定義加載器(defining loader)。也就是說,哪個(gè)類加載器啟動(dòng)類的加載過程并不重要,重要的是最終定義這個(gè)類的加載器。
四、實(shí)現(xiàn)雙親委派的自定義類加載器
如前方法loadClass所示,該方法實(shí)現(xiàn)了雙親委派的邏輯,如果雙親類加載器沒有找到對(duì)應(yīng)的類,最后會(huì)使用findClass來加載類。所以實(shí)現(xiàn)滿足雙親委派的自定義類加載器只需要繼承ClassLoader類,并重寫findClass方法就行。
如下所示:
public class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
private byte[] loadByte(String name) throws Exception {
name = name.replaceAll("\\.", "/");
// 從文件系統(tǒng)中讀取
FileInputStream fis = new FileInputStream(classPath + "/" + name
+ ".class");
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] bytes = loadByte(name);
// 將一個(gè)字節(jié)數(shù)組轉(zhuǎn)換成 Class實(shí)例。
return defineClass(name, bytes, 0, bytes.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
}
MyClass類如下:
public class MyClass {
public void print() {
System.out.println("MyClass, ClassLoader:" + getClass().getClassLoader().getClass().getName());
}
}
執(zhí)行入口:
public static void main(String[] args) throws Exception {
MyClassLoader loader = new MyClassLoader("C:\\work\\leo\\leotest\\myclass\\target\\classes");
Class<?> aClass = loader.loadClass("MyClass");
Object o = aClass.newInstance();
Method printMethod = aClass.getDeclaredMethod("print", null);
printMethod.invoke(o);
}
結(jié)果:
MyClass, ClassLoader:MyClassLoader