Java中由類字節碼流轉化爲JVM運行時類數據必須使用類加載器進行加載,Java中提供了三個類加載器:根類加載器,擴展類加載器,應用程序類加載器,使用的機制可以概括爲“全盤負責雙親委託”機制。
注意圖中的關係是委派關係,不是繼承關係!源碼中使用組合實現,即ClassLoader類的parent成員變量,而最上面的那個類加載器(根類加載器)的parent爲null。
雙親委託機制在代碼中具體體現在ClassLoader類的loadClass方法(下面是JDK1.8代碼):
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
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 thrown if class not found
// from the non-null parent class loader
}
//上面流程委託加載失敗,則使用當前加載器加載
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
//由類全限定名得到Class對象,自定義類加載器(僅)需要重寫該方法
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;
}
}
自定義類加載器
public class MyClassLoader extends ClassLoader {
public MyClassLoader() {
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
//這裏類字節流來源爲class文件
String path = "C:\\Users\\mao\\Desktop\\" + name + ".class";
//讀取class文件,轉化爲字節流
byte[] b = IoUtils.getBytes(path);
return defineClass(name, b, 0, b == null ? 0 : b.length);
}
}
這樣就定義了一個自己的ClassLoader,接下來爲測試代碼,在C:\Users\mao\Desktop\路徑下放一個String.class文件,然後使用該ClassLoader加載的一個class文件:
public class Test {
public static void main(String[] args) throws Exception {
//沒有指定父類加載器,則會被默認設置爲應用程序類加載器爲父類加載器
ClassLoader cl = new MyClassLoader();
Class<?> clazz = Class.forName("String", true, cl);
Object obj = clazz.newInstance();
System.out.println(obj);
}
}
代碼打印:
I am a custom String class
因爲我們放在C:\Users\mao\Desktop\路徑下的String類的toString方法被重寫了:
public class String {
@Override
public java.lang.String toString() {
return "I am a custom String class";
}
}
注意:這個String類是我們自己定義的,而JDK內部也有一個String類,運行時這兩個類當然不是等價的,因此至少類加載器不同(當然上面的例子類全限定名也不同),而我們是無法做到把JDK的String類替換成我們自己定義的String類的(像上面的例子根類加載器會拒絕加載),這也說明了雙親委派機制的一個優點就是安全性高。
另外,Thread類有一個成員變量contextClassLoader,表示線程上下文類加載器,可以通過setContextClassLoader和getContextClassLoader方法設置和獲取該變量的值,默認情況下contextClassLoader的值爲父線程的contextClassLoader的值,而Java中最頂層的線程的contextClassLoader爲應用程序類加載器。這個是在JDK1.2開始引入的,爲了解決“頂層類”需要調用“底層類”卻識別不了的問題。
本博客已停止更新,轉移到微信公衆號上寫文章,歡迎關注:Android進階驛站