三分鐘帶你搞懂雙親委派模型!
什么是雙親委派?
在 Java 虛擬機中,任何一個類由加載它的類加載器和這個類一同來確立其唯一性。
也就是說,JVM 對類的唯一標識,可以簡單的理解為由ClassLoader id + PackageName + ClassName組成,因此在一個運行程序中有可能存在兩個包名和類名完全一致的類,但是如果這兩個類不是由一個 ClassLoader 加載,會被視為兩個不同的類,此時就無法將一個類的實例強轉(zhuǎn)為另外一個類,這就是類加載器的隔離性。
為了解決類加載器的隔離問題,JVM 引入了雙親委派模型。
雙親委派模式,可以用一句話來說表達:任何一個類加載器在接到一個類的加載請求時,都會先讓其父類進行加載,只有父類無法加載(或者沒有父類)的情況下,才嘗試自己加載。
大致流程圖如下:
圖片
使用雙親委派模式,可以保證,每一個類只會有一個類加載器。例如 Java 最基礎(chǔ)的 Object 類,它存放在 rt.jar 之中,這是 Bootstrap 的職責(zé)范圍,當(dāng)向上委派到 Bootstrap 時就會被加載。
但如果沒有使用雙親委派模式,可以任由自定義加載器進行加載的話,Java 這些核心類的 API 就會被隨意篡改,無法做到一致性加載效果。
JDK 中ClassLoader.loadClass()類加載器中的加載類的方法,部分核心源碼如下:
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// 1.首先要保證線程安全
synchronized (getClassLoadingLock(name)) {
// 2.先判斷這個類是否被加載過,如果加載過,直接跳過
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
// 3.有父類,優(yōu)先交給父類嘗試加載;如果為空,使用BootstrapClassLoader類加載器
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 父類加載失敗,這里捕獲異常,但不需要做任何處理
}
// 4.沒有父類,或者父類無法加載,嘗試自己加載
if (c == null) {
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;
}
}
如何自定義類加載器?
針對某些特定場景,比如通過網(wǎng)絡(luò)來傳輸 Java 類的字節(jié)碼文件,為保證安全性,這些字節(jié)碼經(jīng)過了加密處理,這時系統(tǒng)提供的類加載器就無法對其進行加載,此時我們可以自定義一個類加載器來完成文件的加載。
自定義類加載器也需要繼承ClassLoader類,簡單示例如下:
public class CustomClassLoader extends ClassLoader {
private String classPath;
public CustomClassLoader(String classPath) {
this.classPath = classPath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class<?> c = findLoadedClass(name);
if (c == null) {
byte[] data = loadClassData(name);
if (data == null) {
throw new ClassNotFoundException();
}
return defineClass(name, data, 0, data.length);
}
return null;
}
protected byte[] loadClassData(String name) {
try {
// package -> file folder
name = name.replace(".", "http://");
FileInputStream fis = new FileInputStream(new File(classPath + "http://" + name + ".class"));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int len = -1;
byte[] b = new byte[2048];
while ((len = fis.read(b)) != -1) {
baos.write(b, 0, len);
}
fis.close();
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
相關(guān)的測試類如下:
package com.example;
public class ClassLoaderTest {
public static void main(String[] args) {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
System.out.println("current loader:" + loader);
}
}
將ClassLoaderTest.java源文件放在指定目錄下,并通過javac命令編譯成ClassLoaderTest.class,最后進行測試。
public class CustomClassLoaderTest {
public static void main(String[] args) throws Exception {
String classPath = "/Downloads";
CustomClassLoader customClassLoader = new CustomClassLoader(classPath);
Class<?> testClass = customClassLoader.loadClass("com.example.ClassLoaderTest");
Object obj = testClass.newInstance();
System.out.println(obj.getClass().getClassLoader());
}
}
輸出結(jié)果:
com.example.CustomClassLoader@60e53b93
在實際使用過程中,最好不要重寫loadClass方法,避免破壞雙親委派模型。
小結(jié)
雙親委派,指的是在接受類加載請求時,會讓父類加載器試圖加載該類,只有在父類加載器無法加載該類或者沒有父類時,才嘗試從自己的類路徑中加載該類。
其次,針對某些場景,如果要實現(xiàn)類的隔離,可以自定義類加載器來實現(xiàn)特定類的加載。