類加載器ClassLoader:
用於將Java類加載到Java虛擬機中,其常用的有以下三種,當然除此之外還有用戶自定義類加載器
- 根加載器(Bootstrap)
- 擴展加載器
- 應用程序類加載器
根加載器(BootstrapClassLoader):
C++編寫。JVM的核心加載器,隨Java面世的第一版加載器。我們之所以安裝好java,配置好環境變量,運行起java,就可以直接使用Object obj = new Object(),List list = new ArrayList()之類代碼,是因爲根加載器默認將${JAVA_HOME}/jre/lib/rt.jar加載到虛擬機中。
擴展加載器(ExtensionClassLoader):
Java編寫。由於Java誕生後不斷的擴展優化,單獨使用擴展類加載器加載${JAVA_HOME}/jre/lib/ext目錄下的jar,庫名通常以javax開頭,例如swing等;擴展類庫主要是爲了兼容舊版本,但某些東西又有了新的解決方案,於是提供擴展類庫。
應用程序類加載器(AppClassLoader):
面向程序員的加載器,會加載環境變量中CLASSPATH下的jar,自己生成的類或者第三方的類均有該加載器加載。
程序測試:
List list = new ArrayList();// java包
SwingNode swingNode = new SwingNode();// javax包
Student student = new Student();// 自定義java類
System.out.println(list.getClass().getClassLoader());
System.out.println(swingNode.getClass().getClassLoader());
System.out.println(student.getClass().getClassLoader());
運行結果:
可以看到三種java文件對應的類加載器,根加載器爲null,擴展加載器及應用程序加載器都有一個sun.misc.Launcher前綴,實際上看下圖路徑,該Launcher類就是JVM的入口。
通過代碼體現父類繼承關係:
List list = new ArrayList();
SwingNode swingNode = new SwingNode();
Student student = new Student();
System.out.println(swingNode.getClass().getClassLoader().getParent());
System.out.println(student.getClass().getClassLoader().getParent());
System.out.println(student.getClass().getClassLoader().getParent().getParent());
運行結果:
雙親委派模型
當某個類加載器需要加載某個.class文件時,它首先把這個任務委託給他的父類加載器,如果父的類加載器沒有加載,子類加載器纔會嘗試去加載。通俗的說就是:不論哪個class文件需要加載,首先給根加載器,若根加載器沒找到,則交給擴展類加載器,如果還沒找到,纔會給應用程序類加載器,再找不到就會報ClassNotFound異常。
ClassLoader源碼分析
ClassLoader爲一個抽象類,在虛擬機啓動後進行類加載會首先調用loadClassInternal方法,而該方法指向loadClass方法加載class文件,而雙親委派模型就是在該方法中實現。該方法首先判斷該類是否已經被加載,若沒被加載,則調用parent.loadClass方法加載(即父加載器),parent.loadClass方法中依然會判斷parent是否爲null,若依然有父類加載器,則繼續遞歸向上調用,直至parent==null。當其父加載器拋出ClassNotFoundException時,說明沒找到,則由子加載器繼續加載,符合雙親委派機制。
public abstract class ClassLoader {
private final ClassLoader parent;
// 該方法在虛擬機啓動進行類加載時會立刻調用
private Class<?> loadClassInternal(String name)
throws ClassNotFoundException
{
if (parallelLockMap == null) {
synchronized (this) {
return loadClass(name);
}
} else {
return loadClass(name);
}
}
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先,檢查請求的類是否已經被加載過了
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 如果父類加載器拋出ClassNotFoundException
// 說明父類加載器無法完成加載請求
}
if (c == null) {
// 在父類加載器無法加載時
// 再調用本身的findClass方法進行類加載
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
}
好處
保證源代碼不受污染。假設你自己定義了一個java.lang.String類,去運行,若沒有此機制,會污染原本jdk中帶的String類,導致代碼出現不可預知的問題,程序將變得一片混亂。
破壞雙親委派模型
破壞歷史
在Java誕生以來,歷史上有三次大規模破壞:
第一次:雙親委派模型爲JDK1.2推出,但在此之前就已經存在了ClassLoader,因此需要兼容。上述ClassLoader源碼在loadClass方法中實現的雙親委派,因此如果想破壞最直接的辦法就是繼承ClassLoader並重寫loadClass方法,該方法無疑是破壞性的,但因爲JDK1.2以前都是這麼做的,該接口無奈保留支持重寫,但新的代碼絕對不推薦如此使用。
ClassLoader在JDK1.2之後新增了一個findClass方法,目的就是當父類加載器加載失敗時,將子類加載器的邏輯寫在findClass中完成自定義設計。
第二次:
什麼時候需要打破
未完待續。