分析JVM雙親委派模型的類載入原始碼 自定義類載入器
阿新 • • 發佈:2019-01-06
雙親委派模型下,在父類載入器無法載入的情況下再由當前類載入器去載入。具體的實現邏輯在java.util.ClassLoader抽象類的loadClass方法中。在該方法中,先檢查是否已經載入過,如果沒有,就讓父類載入器載入。如果父類載入器服務載入,就呼叫findClass方法載入。findClass方法是空的,需要使用者過載它。所以,按照這樣的邏輯,我們在使用loadClass之前需要過載findClass方法。
Boostrap ClassLoader | Extension ClassLoader | Application ClassLoader | | OtherClassLoader1 OtherClassLoader2 ...
- loadClass
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
- loadClass(String name, boolean resolve):
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 1. 檢查是否已經載入過 Class c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { //當前載入器存在父類載入器,遞迴呼叫父類載入器 c = parent.loadClass(name, false); } else { //檢查該類是否被Bootstrap載入器載入過,有則返回,否則返回null 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(); //既然父類載入器無法載入,則就是用使用者自己所過載的類載入器 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) { //連結link所載入的類。該方法名起得不好,有誤導性 resolveClass(c); } return c; } }
- find Class
findClass的預設實現如下:
protected Class<?> findClass(String name) throws ClassNotFoundException {
//如果沒有過載,一呼叫就丟擲異常
throw new ClassNotFoundException(name);
}
findClass的返回值是Class型別,而我們可以使用defineClass方法將一個byte[]轉換成Class型別。而byte[]資料可以通過檔案讀取class位元組碼檔案獲取。
- defineClass
defineClass定義和預設實現如下:
protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError {
return defineClass(name, b, off, len, null);
}
簡單示例
首先,我們定義一個待載入的普通Java類:Student.java, 其中package為空,可以在任何路徑下編譯該檔案成class位元組碼檔案。
public class Student {
public void say(){
System.out.println("hello world");
}
}
進入window的cmd命令列視窗,進入Student.java所在目錄,使用命令javac Student.java
獲得Student.class檔案。
- 載入Student.class檔案
接下來就是自定義我們的類載入器。閱讀下面的程式碼,細心的讀者會發現,findClass方法是過載的方法,不過載的話無法執行。下面這段程式碼可以在電腦任何位置執行javac Test.java
和java Test
來進行測試。
mport java.lang.reflect.Method;
import java.io.FileInputStream;
class MyClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte();
//將byte[]資料轉換成Class型別的物件
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
//從硬碟中讀取class位元組碼檔案,並轉換成byte[]資料
private byte[] loadByte() throws Exception {
String name = "C:/Users/USER/Desktop/temp/Student.class";
FileInputStream fis = new FileInputStream(name);
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
};
public class Test {
public static void main(String args[]) throws Exception {
MyClassLoader classLoader = new MyClassLoader();
//載入Student的Class型別物件
Class clazz = classLoader.loadClass("Student");
Object obj = clazz.newInstance();
//使用反射的方式獲取和執行say()方法
Method helloMethod = clazz.getDeclaredMethod("say", null);
helloMethod.invoke(obj, null);
}
}
謝謝!