1 什么是ClassLoader?
- 1 大家都知道拨脉,當(dāng)我們寫好一個(gè)Java程序之后赁温,不是管是CS還是BS應(yīng)用界牡,都是由若干個(gè).class文件組織而成的一個(gè)完整的Java應(yīng)用程序冈欢,當(dāng)程序在運(yùn)行時(shí),即會(huì)調(diào)用該程序的一個(gè)入口函數(shù)來調(diào)用系統(tǒng)的相關(guān)功能谬晕,而這些功能都被封裝在不同的class文件當(dāng)中碘裕,所以經(jīng)常要從這個(gè)class文件中要調(diào)用另外一個(gè)class文件中的方法,如果另外一個(gè)文件不存在攒钳,則會(huì)引發(fā)系統(tǒng)異常帮孔。
- 2 而程序在啟動(dòng)的時(shí)候,并不會(huì)一次性加載程序所要用的所有class文件不撑,而是根據(jù)程序的需要文兢,通過Java的類加載機(jī)制(ClassLoader)來動(dòng)態(tài)加載某個(gè)class文件到內(nèi)存當(dāng)中的,從而只有class文件被載入到了內(nèi)存之后焕檬,才能被其它c(diǎn)lass所引用姆坚。所以ClassLoader就是用來動(dòng)態(tài)加載class文件到內(nèi)存當(dāng)中用的。
2 Java提供的三個(gè)默認(rèn)ClassLoader
- 1 BootStrap ClassLoader:稱為啟動(dòng)類加載器实愚,是Java類加載層次中最頂層的類加載器兼呵,負(fù)責(zé)加載JDK中的核心類庫,如:rt.jar腊敲、resources.jar击喂、charsets.jar等,可通過查找sun.boot.class.path這個(gè)系統(tǒng)屬性碰辅,得知該類加載器從哪些地方加載了相關(guān)的jar或class文件懂昂。
System.out.println(System.getProperty("sun.boot.class.path"));
輸出結(jié)果為:
C:\Program Files\Java\jdk1.6.0_22\jre\lib\resources.jar;
C:\Program Files\Java\jdk1.6.0_22\jre\lib\rt.jar;
C:\Program Files\Java\jdk1.6.0_22\jre\lib\sunrsasign.jar;
C:\Program Files\Java\jdk1.6.0_22\jre\lib\jsse.jar;
C:\Program Files\Java\jdk1.6.0_22\jre\lib\jce.jar;
C:\Program Files\Java\jdk1.6.0_22\jre\lib\charsets.jar;
C:\Program Files\Java\jdk1.6.0_22\jre\classes
- 2 Extension ClassLoader:稱為擴(kuò)展類加載器,負(fù)責(zé)加載Java的擴(kuò)展類庫没宾,默認(rèn)加載JAVA_HOME/jre/lib/ext/目下的所有jar忍法。
- 3 App ClassLoader:稱為系統(tǒng)類加載器,負(fù)責(zé)加載應(yīng)用程序classpath目錄下的所有jar和class文件榕吼。
- 4 總結(jié)
Extension ClassLoader和App ClassLoader也繼承自java.lang.ClassLoader類,但是Bootstrap ClassLoader不繼承自java.lang.ClassLoader類勉失,因?yàn)樗皇且粋€(gè)普通的Java類羹蚣,底層由C++編寫,已嵌入到了JVM內(nèi)核當(dāng)中乱凿,當(dāng)JVM啟動(dòng)后顽素,Bootstrap ClassLoader也隨著啟動(dòng)咽弦,負(fù)責(zé)加載完核心類庫后,并構(gòu)造Extension ClassLoader和App ClassLoader類加載器胁出。
3 ClassLoader加載類的原理
- 1 原理簡介
1)ClassLoader使用的是雙親委托模型來搜索類的型型,每個(gè)ClassLoader實(shí)例都有一個(gè)父類加載器的引用(不是繼承的關(guān)系,是一個(gè)包含的關(guān)系)全蝶。
2)類加載器的包含關(guān)系如下:
自定義類加載器————>系統(tǒng)類加載器————>擴(kuò)展類加載器————>根加載器
3)當(dāng)一個(gè)自定義ClassLoader實(shí)例需要加載某個(gè)類時(shí)闹蒜,先從自己的命名空間里查找是否已加載該類,如果已加載抑淫,則直接返回代表該類的Class對(duì)象的引用绷落。
4)如果沒加載,才委托其父類加載器進(jìn)行加載始苇。其父類加載器命名空間沒有在砌烁,則一層層向上委托,直到根加載器催式。然后由根加載器開始從上到下加載該類函喉。一旦加載到則將這個(gè)找到的類生成一個(gè)類的定義,并將它加載到內(nèi)存當(dāng)中荣月,最后返回這個(gè)類在內(nèi)存中的Class實(shí)例對(duì)象的引用管呵。
5)如果所有加載器都沒有加載到這個(gè)類時(shí),則拋出ClassNotFoundException異常喉童。 - 2 為什么要使用雙親委托這種模型呢撇寞?
1)這樣可以避免重復(fù)加載,當(dāng)父親已經(jīng)加載了該類的時(shí)候堂氯,就沒有必要子ClassLoader再加載一次蔑担。
2)考慮到安全因素,我們試想一下咽白,如果不使用這種委托模式啤握,那我們就可以隨時(shí)使用自定義的String來動(dòng)態(tài)替代java核心api中定義的類型,這樣會(huì)存在非常大的安全隱患晶框,而雙親委托的方式排抬,就可以避免這種情況,因?yàn)镾tring已經(jīng)在啟動(dòng)時(shí)就被引導(dǎo)類加載器(Bootstrcp ClassLoader)加載授段,所以用戶自定義的ClassLoader永遠(yuǎn)也無法加載一個(gè)自己寫的String蹲蒲,除非你改變JDK中ClassLoader搜索類的默認(rèn)算法。 - 3 JVM在搜索類的時(shí)候侵贵,又是如何判定兩個(gè)class是相同的呢届搁?
1)JVM在判定兩個(gè)class是否相同時(shí),不僅要判斷兩個(gè)類名是否相同,而且要判斷是否由同一個(gè)類加載器實(shí)例加載的卡睦。只有兩者同時(shí)滿足的情況下宴胧,JVM才認(rèn)為這兩個(gè)class是相同的。就算兩個(gè)class是同一份class字節(jié)碼表锻,如果被兩個(gè)不同的ClassLoader實(shí)例所加載恕齐,JVM也會(huì)認(rèn)為它們是兩個(gè)不同class。
2)比如網(wǎng)絡(luò)上的一個(gè)Java類瞬逊,javac編譯之后生成字節(jié)碼文件显歧,同一個(gè)自定義類加載器的兩個(gè)實(shí)例同時(shí)讀取了這個(gè)字節(jié)碼文件,并分別定義出了java.lang.Class實(shí)例來表示這個(gè)類码耐,對(duì)于JVM來說追迟,它們是兩個(gè)不同的實(shí)例對(duì)象,但它們確實(shí)是同一份字節(jié)碼文件骚腥,如果試圖將這個(gè)Class實(shí)例生成具體的對(duì)象進(jìn)行轉(zhuǎn)換時(shí)敦间,就會(huì)拋運(yùn)行時(shí)異常java.lang.ClassCaseException,提示這是兩個(gè)不同的類型束铭。
4 定義自已的ClassLoader
- 1 既然JVM已經(jīng)提供了默認(rèn)的類加載器廓块,為什么還要定義自已的類加載器呢?
因?yàn)镴ava中提供的默認(rèn)ClassLoader契沫,只加載指定目錄下的jar和class带猴,如果我們想加載其它位置的類或jar時(shí),比如:我要加載網(wǎng)絡(luò)上的一個(gè)class文件懈万,通過動(dòng)態(tài)加載到內(nèi)存之后拴清,要調(diào)用這個(gè)類中的方法實(shí)現(xiàn)我的業(yè)務(wù)邏輯。在這樣的情況下会通,默認(rèn)的ClassLoader就不能滿足我們的需求了口予,所以需要定義自己的ClassLoader。 - 2 定義自已的類加載器分為兩步:
1)繼承java.lang.ClassLoader
2)重寫父類的findClass方法 - 3 父類有那么多方法涕侈,為什么偏偏只重寫findClass方法沪停?
因?yàn)镴DK已經(jīng)在loadClass方法中幫我們實(shí)現(xiàn)了ClassLoader搜索類的算法,當(dāng)在loadClass方法中搜索不到類時(shí)裳涛,loadClass方法就會(huì)調(diào)用findClass方法來搜索類木张,所以我們只需重寫該方法即可。 - 4 代碼舉例
package lwlstudy.test.classloader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
public String getClassPath() {
return classPath;
}
public void setClassPath(String classPath) {
this.classPath = classPath;
}
public MyClassLoader(ClassLoader parent) {
super(parent);
}
@Override
protected Class<?> findClass(String className) throws ClassNotFoundException {
byte[] data = this.getData(className);
if (data == null) {
throw new ClassNotFoundException();
} else {
return defineClass(className, data, 0, data.length);
}
}
private byte[] getData(String className) {
String path = classPath + File.separatorChar + className.replace('.', File.separatorChar) + ".class";
InputStream is = null;
ByteArrayOutputStream stream = null;
try {
is = new FileInputStream(new File(path));
stream = new ByteArrayOutputStream();
byte[] buffer = new byte[2048];
int num = 0;
while ((num = is.read(buffer)) != -1) {
stream.write(buffer, 0, num);
}
return stream.toByteArray();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
is.close();
stream.close();
} catch (IOException e2) {
e2.printStackTrace();
}
}
return null;
}
public static void main(String[] args) throws Exception {
MyClassLoader mcl1 = new MyClassLoader("W:\\zz");
Class<?> clazz = mcl1.loadClass("lwlstudy.test.classloader.Dog");
Object obj = clazz.newInstance();
Object obj1 = clazz.newInstance();
System.out.println(obj.getClass().equals(obj1.getClass()));
MyClassLoader mcl2 = new MyClassLoader("W:\\zz");
Class<?> clazz2 = mcl2.loadClass("lwlstudy.test.classloader.Dog");
Object obj2 = clazz2.newInstance();
System.out.println(obj.getClass().equals(obj2.getClass()));
}
}
```
輸出結(jié)果為:
```java
true
false
```
原因參考:如何判定兩個(gè)class是相同的呢端三?