雙親委派
雙親委派:如果一個類加載器收到了加載某個類的請求,則該類加載器並不會去加載該類,而是把這個請求委派給”父類加載器“,每一個層次的類加載器都是如此,因此所有的類加載請求最終都會傳送到頂端的啓動類加載器;只有當”父類加載器“在其搜索範圍內無法找到所需的類,並將該結果反饋給子類加載器,子類加載器會嘗試去自己加載。
jvm中加載器層次關係:
啓動類加載器BootStrapClassLoader --------> 擴展類加載器ExtensionClassLoader -------->
應用加載器AppClassLoader --------> 自定義加載器
-
BootStrapClassLoader:
加載/lib/下的jar(包括Java核心類庫rt.jar等其他),因爲是C++寫的,程序裏面打印出來是null
官方註釋:Some implementations may use null to represent the bootstrap class loader
-
ExtensionClassLoader:
加載/lib/ext/下的jar,Java實現,可以在程序裏裏面獲取
-
AppClassLoader:
又叫SystemClassLoader,classpath下面的即我們寫的代碼,以及第三方引入的依賴,都是他加載的
示例代碼:
package day20200228;
import com.sun.nio.zipfs.ZipFileSystem;
import junit.extensions.TestDecorator;
import sun.net.spi.nameservice.dns.DNSNameService;
import javax.crypto.Cipher;
/**
* @Author: xiaoshijiu
* @Date: 2020/2/29
* @Description: $value$
*/
public class TestClassLoad {
public static void main(String[] args) {
/**
* BootStrapClassLoad
*/
System.out.println(Object.class.getClassLoader()); // rt.jar
System.out.println(String.class.getClassLoader()); // rt.jar
System.out.println(Math.class.getClassLoader()); // rt.jar
System.out.println(Cipher.class.getClassLoader()); // jce.jar
System.out.println("================");
/**
* ExtensionClassLoad
*/
System.out.println(ZipFileSystem.class.getClassLoader()); // ext/zipfs.jar
System.out.println(DNSNameService.class.getClassLoader()); // ext/dnsns.jar
System.out.println("================");
/**
* AppClassLoad/SystemClassLoad
*/
System.out.println(TestClassLoad.class.getClassLoader()); // 自己寫的
System.out.println(ClassLoadingProcessStatic.class.getClassLoader()); // 自己寫的
System.out.println(TestDecorator.class.getClassLoader()); // 引入的junit依賴中的
}
}
運行結果
雙親委派的源碼實現
在java.lang.ClassLoader
中的,通過loadClass
方法加載類,也就是雙親委派的具體實現
// name參數,即binary name,類的全類名
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先,檢查這個類是不是被加載過了;findLoadedClass方法檢查
Class<?> c = findLoadedClass(name);
// 沒有被加載過
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
// ”父類“不爲空,找父類加載器加載;loadClass方法:仍然調用該方法,即產生遞歸;
c = parent.loadClass(name, false);
} else {
// ”父類“爲空,即代表”父類“爲BootstrapClassLoader,找其加載
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
//異常
}
if (c == null) {
long t1 = System.nanoTime();
// 如果還沒有加載到,c仍然爲空,那麼調用該加載器的findClass方法,去加載
c = findClass(name);
// 記錄時間
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.ClassLoader
中的findClass
方法
空方法,沒有實現,所以要想自定義類加載器,必須要繼承基類並重寫該findClass
方法
`findClass`方法將 binary name 變成 Class 對象
這裏有一個關於binary name的解釋:
$ 表示裏面的內部類,$ 數字裏面的第幾個匿名內部類
自定義類加載器
前面說了要想自定義類加載器,必須要繼承基類並重寫該findClass
方法
findClass方法 將 binary name 變成 Class 對象
另外在結合: defineClass方法 可以將class文件內容的字節數組轉成Class對象
defineClass方法 在基類`java.lang.ClassLoader`中有實現
這樣一來,我們只需要 根據class文件名 binary name 獲得 class文件內容的byte[]形式
再根據defineClass方法,將byte[]數據變爲Class對象,完成。
package day20200229;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
/**
* @Author: xiaoshijiu
* @Date: 2020/2/29
* @Description: 自定義 ClassLoader
* 繼承 基類ClassLoader,重寫 findClass方法
* findClass方法:將 binary name 變成 Class對象
* 其中還需要另外一個方法 defineClass :可以將class文件的字節數組轉成Class對象
*/
public class MyClassLoader extends ClassLoader {
static String path;
public MyClassLoader() {
super();
}
// 指定 上級類加載器的構造方法
public MyClassLoader(ClassLoader parent) {
super(parent);
}
/**
* @param name binary name
*/
@Override
protected Class<?> findClass(String name) {
byte[] bytes = classToBytes(name);
return defineClass(name, bytes, 0, bytes.length);
}
/**
* 抽取出來一個私有方法,將class文件內容變成bytes數組
*
* @param name binary name
*/
private byte[] classToBytes(String name) {
FileInputStream inputStream = null;
ByteArrayOutputStream outputStream = null;
try {
// 判空
if (name == null || "".equals(name)) {
return null;
}
// 加載指定path路徑的class文件
if (path == null) {
// 文件位置,.替換成/
name = name.replace(".", "/") + ".class";
} else {
name = path + name.replace(".", "/") + ".class";
}
inputStream = new FileInputStream(name);
outputStream = new ByteArrayOutputStream();
// 一次接受50字節
byte[] bytes = new byte[50];
int len;
while ((len = inputStream.read(bytes)) != -1) {
outputStream.write(bytes, 0, len);
}
return outputStream.toByteArray();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
// 測試代碼
public static void main(String[] args) throws ClassNotFoundException {
MyClassLoader classLoader = new MyClassLoader();
// classpath下的類(自己寫的類),雙親委派機制,由AppClassLoader加載
Class<?> c = classLoader.loadClass("day20200222.TestVolatile");
System.out.println(c.getClassLoader());
// 硬盤中的類(g:/day20190820.ListNodeSum.class),雙親都加載不了,由該自定義的加載器加載
path = "g:/";
Class<?> c2 = classLoader.loadClass("day20190820.ListNodeSum");
System.out.println(c2.getClassLoader());
}
}
上面兩組測試代碼:
- 第一,
day20200222.TestVolatile該類位於當前項目工程中,即classpath下,根據雙親委派機制,由AppClassLoader加載
- 第二,
day20190820.ListNodeSum位於g盤中,根據雙親委派機制,”父類加載器“均不能加載,最後反饋到該類加載器,其findClass方法可以正確加載,即自定義加載器完成加載
運行結果