在Java虛擬機中,Java類可以被動態裝載到 Java 虛擬機中並執行。加載(Loading)指尋找一個具有特定名稱的類或者接口類型的二進制形式,並且用這個二進制形式構造一個代表該類或者接口的Class對象的進程。
由類ClassLoader和它的子類實現的類裝載器負責加載進程,讀取Java 字節代碼,並轉換成java.lang.Class類的一個實例。
Java中系統提供的類加載器主要有下面三個:
1. 引導類加載器(bootstrap classloader):用來加載 Java 的核心庫,是用原生代碼來實現的,並不繼承自java.lang.ClassLoader。
2. 擴展類加載器(extensions classloader):用來加載 Java 的擴展庫。該類加載器在擴展庫目錄(ext)裏面查找並加載 Java 類。
3. 系統類加載器(system classloader):它根據 Java 應用的類路徑(CLASSPATH)來加載 Java 類。一般來說,Java 應用的類都是由它來完成加載的。可以通過ClassLoader.getSystemClassLoader() 來獲取它。
下面的示例展示了這三種類加載器各自的作用:
public class LoaderSample {
public static void main(String[] args) {
Class<?> c;
ClassLoader cl;
try {
c = Class.forName("java.lang.Object");
cl = c.getClassLoader();// 啓動類裝載器 (bootstrap)
System.out.println(" java.lang.Object's loader is " + cl);
c = Class.forName("sun.net.spi.nameservice.dns.DNSNameService");
cl = c.getClassLoader();// 擴展類裝載器
System.out.println(" sun.net.spi.nameservice.dns.DNSNameService's loader is "+ cl);
c = Class.forName("LoaderSample");
cl = c.getClassLoader();// 系統類裝載器
System.out.println(" LoaderSample's loader is " + cl);
} catch (Exception e) {
e.printStackTrace();
}
}
}
運行結果:
java.lang.Object's loader is null
sun.net.spi.nameservice.dns.DNSNameService's loader is sun.misc.Launcher$ExtClassLoader@42e816
LoaderSample's loader is sun.misc.Launcher$AppClassLoader@addbf1
類加載器的繼承關係:
類加載器樹狀組織結構:
類加載器在加載某個類時,會首先代理給其父類加載器,由父類加載器先去嘗試加載這個類,依次類推。
ClassLoader類中的loadClass(String name, boolean resolve)實現:
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
可以看出,loadClass()方法會首先調用findLoadedClass() 方法來檢查該類是否已經被加載過;如果沒有加載過的話,會按照代理模式調用父類加載器的loadClass() 方法來嘗試加載該類。如果兩種方式都未成功加載的話,則調用 findClass() 方法來查找該類。
當自定義繼承自ClassLoader的類加載器時,只需重寫findClass(String name)即可,保留loadClass()的代理模式的實現。
由於代理模式,啓動加載過程的類加載器和完成類的加載工作的類加載器可能不是同一個。啓動加載過程的類加載器通過調用loadClass實現,被稱爲初始類加載器;完成類的加載工作的類加載器最終調用defineClass將字節碼轉換成Java類,被稱爲定義類加載器。一個類的定義類加載器是它所引用的其他類的初始類加載器(這也不難理解,一個類最終由定義類加載器載入虛擬機,此時需要載入它的引用類,所用的類加載器則使用該定義類加載器)。
在 Java 虛擬機判斷兩個類是否相同的時候,使用的是類的定義加載器。
public class Sample {
private Sample instance;
public void setSample(Object instance) {
this.instance = (Sample) instance;
}
}
public void testClassIdentity() {
String classDataRootPath = "E:\\workspace\\Classloader ";
FileSystemClassLoader fscl1 = new FileSystemClassLoader(classDataRootPath);
FileSystemClassLoader fscl2 = new FileSystemClassLoader(classDataRootPath);
String className = " Sample";
try {
Class<?> class1 = fscl1.loadClass(className);
Object obj1 = class1.newInstance();
System.out.println(fscl1);
System.out.println("class1 loaded!");
System.out.println(class1.getClassLoader());
Class<?> class2 = fscl2.loadClass(className);
Object obj2 = class2.newInstance();
Class<?> class2 = fscl2.loadClass(className);
System.out.println(fscl2);
System.out.println("class2 loaded!");
System.out.println(class2.getClassLoader());
Method setSampleMethod = class1.getMethod("setSample", java.lang.Object.class);
setSampleMethod.invoke(obj1, obj2);
} catch (Exception e) {
e.printStackTrace();
}
}
運行結果:
FileSystemClassLoader@61de33
class1 loaded!
sun.misc.Launcher$AppClassLoader@addbf1
FileSystemClassLoader@14318bb
class2 loaded!
sun.misc.Launcher$AppClassLoader@addbf1
可以看出兩個Class的初始類加載器雖然不同,定義類加載器卻同是系統類加載器的一個實例,因此兩個Class完全相同。由此可以推斷,繼承自ClassLoader的自定義類加載器,由於無法重寫defineClass方法(final),同一個類即使由不同的自定義類加載器實例加載,虛擬機依然認爲是相同的類。
參考:
http://www.ibm.com/developerworks/cn/java/j-lo-classloader/index.html#fig1