Java虛擬機器之類載入器
類載入器介紹
類載入器負責將class檔案載入到記憶體中,併為之生成對應的java.lang.Class物件。對於任意一個類,都需要載入它的類載入器和這個類本身來確定該類在JVM中唯一性,也就是說,同一個class檔案用兩個不同的類載入器載入並建立兩個java.lang.Class物件,即使兩個物件來源自同一個class檔案,它們也是不相等的,這裡“相等”包括Class物件的equals()方法、isAssignableFrom()方法、isInstance()方法,也包括使用instanceof關鍵字做物件所屬關係判定情況。
類載入器分類
虛擬機器提供了3種類載入器,啟動(Bootstrap)類載入器、擴充套件(Extension)類載入器、應用程式(Application)類載入器(也稱應用類載入器)
Bootstrap ClassLoader
該類載入器沒有父類載入器,它負責載入虛擬機器的核心類庫。Bootstrap ClassLoader載入器用於在啟動JVM時載入類,以使JVM能正常工作,因而它是用Native(c++)程式碼實現的,最早被創建出來,處於最底層。它並沒有繼承java.lang.ClassLoader類。
我們可以來看下Bootstrap ClassLoader載入了哪些類,檢視Bootstrap ClassLoader載入的類可以通過System.getProperty("sun.boot.class.path")看到:
public class ClassLoaderTest { public static void main(String[] args) { String path = System.getProperty("sun.boot.class.path"); Arrays.asList(path.split(";")).forEach(System.out::println); } }
結果:
Extension ClassLoader
該類載入器的父類載入器是根類載入器。它從java.ext.dirs系統屬性所指定的目錄獲取載入類庫或從JDK的安裝目錄的jre\lib\ext子目錄下載入類庫。如果把jar放到這個目錄下,也會自動用擴充套件類載入器載入。擴充套件類載入器是java類,是java.lang.ClassLoader類的子類。
Application ClassLoader
應用類載入器它的父類載入器是擴充套件類載入器,它將載入CLASSPATH中配置的目錄和jar檔案,它是使用者自定義類載入器的預設父類載入器,系統類載入器是java類,是java.lang.ClassLoader類的子類。
類載入器之間的關係可用如圖表示:
自定義類載入器
使用者可以自定義類載入器,自定義類載入器只需要繼承java.lang.ClassLoader,重寫父類的findClass方法即可完成自定義類載入器的編寫。
我們自己動手寫一個類載入器,載入位於D盤根目錄下的類:
public class MyClassLoader extends ClassLoader {
private final String dir = "D:/";
//重寫父類findClass方法
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
//Java包是以目錄形式存在在磁碟中,所以我們需要將.替換成/
String className = name.replace(".", "/");
File classFile = new File(dir, className + ".class");
if (!classFile.exists()) {
throw new ClassNotFoundException();
}
//將類讀成位元組陣列
byte[] classBytes = loadClassBytes(classFile);
if (classBytes == null || classBytes.length == 0) {
throw new ClassNotFoundException();
}
return this.defineClass(name, classBytes, 0, classBytes.length);
}
private byte[] loadClassBytes(File classFile) {
//JDK7語法糖,try with resource語法,可以不用手動的關閉資源
try (ByteArrayOutputStream bout = new ByteArrayOutputStream();
FileInputStream fin = new FileInputStream(classFile)) {
byte[] buffer = new byte[1024];
int len;
while ((len = fin.read(buffer)) != -1) {
bout.write(buffer, 0, len);
}
bout.flush();
return bout.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
新建一個HelloWorld類編譯後放在D盤下:
public class HelloWorld {
public String hello() {
return "Hello World!";
}
}
測試我們的類載入器:
public class ClassLoaderTest {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
MyClassLoader classLoader = new MyClassLoader();
Class<?> clazz = classLoader.loadClass("com.satra.classloader.HelloWorld");
System.out.println(clazz);
System.out.println(clazz.getClassLoader());
Object obj = clazz.newInstance();
Method method = clazz.getMethod("hello", null);
Object o = method.invoke(obj, null);
System.out.println(o);
}
}
輸出結果:
類載入器雙親委派模型
前面我們說過,同一個class檔案用兩個不同的類載入器載入並建立兩個java.lang.Class物件,即使兩個物件來源自同一個class檔案,它們也是不相等的。例如當我們自定義的類載入器載入了java.lang包中的String類,這樣會造成記憶體中存在兩個String的Class物件,而這兩個Class物件的例項物件的eques會不相等,對虛擬機器的穩定執行造成危害。所以虛擬機器定義了雙親委派模型來解決這個問題。
雙親委派模型是指:某個特定的類載入器在接到載入類的請求時,首先將載入任務委託給父類載入器,依次遞迴,如果父類載入器可以完成類載入任務,就成功返回;只有父類載入器無法完成此載入任務時,才自己去載入。
我們可以看下JDK類載入器原始碼的實現:
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 {
//沒有父類說明這個類載入器是Bootstrap類載入器呼叫本地方法去載入
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
//有父類且父類不能完成載入或者沒有父類時,呼叫findClass完成載入
if (c == null) {
long t1 = System.nanoTime();
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虛擬機器:JVM高階特性與最佳實踐》 周志明著