推薦視訊教程download:
一、何為類加載器
我們編寫的.java檔案經過編譯器編譯之後,生成.class檔案,即位元組碼檔案,類加載器就是負責加載位元組碼檔案到JVM中,并将位元組碼轉換成為java.lang.class類的執行個體,這個執行個體便是我們編寫的類,通過class執行個體的newInstance方法,便可以得到java類的對象。
類加載器是類加載過程中的關鍵角色,他存在于「類加載Class load」過程的「加載」階段中,在這個階段,JVM虛拟機完成了三件事情:
- 通過一個類的全限定名(包名稱+類名稱)擷取定義此類的二進制位元組流(類的權限定名可以映射到檔案系統中的檔案路徑);
- 将這個位元組流所代表的靜态存儲結構轉化為方法區的運作時資料結構;
- 在記憶體中生成一個代表這個類的java.lang.Class對象,作為方法區這個類的各種資料的通路入口;
java.lang.ClassLoader
- loadClass(String className): 加載className類,傳回java.lang.class類的執行個體,異常則抛出ClassNotFoundException
- defineClass(String name, byte[] b, int off, int len): 加載位元組碼,傳回class類的執行個體,異常則抛出NoClassDefFoundError
二、類加載器的體系結構
-
啟動類加載器「Bootstrap ClassLoader」
處于最頂端的類加載器,主要負責JAVA_HOME/jre/lib目錄下的核心jar或者由-Xbootclasspath選項指定的jar包的裝入工作。深入分析下Launcher的源碼,發現Bootstrap ClassLoader其實加載的是System.getProperty("sun.boot.class.path")定義下的類包路徑。
檢視JVM啟動後Bootstrap ClassLoader具體加載了哪些jar:
URL[] bootUrls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
for (URL url : bootUrls) {
System.out.println(url.toExternalForm());
}
Bootstrap ClassLoader是由C++編寫的并且内嵌于JVM中,該加載器是無法被java程式直接引用的。比如,java.util.ArrayList類處于rt.jar包下,該包是由Bootstrap ClassLoader負責加載,是以下面這段代碼列印出來就是null了。
ArrayList list = new ArrayList();
System.out.println("list的類加載器為:"+list.getClass().getClassLoader());
-
擴充類加載器「Extension ClassLoader」
擴充類加載器是由sun.misc.Launcher$ExtClassLoader實作,顧名思義這個類加載器主要負責加載JAVA_HOME\lib\ext目錄中或者被java.ext.dirs系統變量定義的路徑下的所有類庫。
-
應用程式類加載器「App ClassLoader」
應用程式類加載器是由sun.misc.Launcher$AppClassLoader實作,通過源碼發現,該類加載器負責加載System.getProperty("java.class.path")也就是classpath下的類庫。該類加載器又可以稱為系統類加載器,在使用者沒有明确指定類加載器的情況下,系統預設使用AppClassLoader加載類。
-
自定義類加載器「Custom ClassLoader」
自定義類加載器是提供給使用者自定義「加載哪裡的類」而産生的,當初虛拟機在定義「通過一個類的全限定名(包名稱+類名稱)擷取定義此類的二進制位元組流」并沒有把擷取方式限定死,提供了靈活的方式給使用者使用,被加載的類可以來自于資料庫、可以來自本地檔案、可以來自雲存儲媒體等等,使用者所需要的就是自定義類加載器并且繼承ClassLoader,最後重寫「findClass」方法,ClassLoader為我們提供了defineClass方法可以友善的加載源碼的二進制位元組流。
/*
* for example, an application could create a network class loader to
* download class files from a server. Sample code might look like:
*/
ClassLoader loader = new NetworkClassLoader(host,port);
Object main = loader.loadClass("Main", true).newInstance();
/*
*The network class loader subclass must define the methods {@link
* #findClass <tt>findClass</tt>} and <tt>loadClassData</tt> to load a class
* from the network. Once it has downloaded the bytes that make up the class,
* it should use the method {@link #defineClass <tt>defineClass</tt>} to
* create a class instance. A sample implementation is:
*/
class NetworkClassLoader extends ClassLoader {
String host;
int port;
public Class findClass(String name) {
byte[] b = loadClassData(name);
return defineClass(name, b, 0, b.length);
}
private byte[] loadClassData(String name) {
// load the class data from the connection
}
}
classloader.png
三、雙親委派模型
在同一個JVM中,一個類加載器和一個類的全限定名共同唯一決定一個類Class的執行個體。也就是說判定兩個類相等法則:類的全名(包名+類名)相同+類加載器相同。
類加載器在加載類的過程中,會先代理給它的父加載器,以此類推。這樣的好處是能夠保證Java核心庫的類型安全,例如java.lang.String類,如果不存在代理模式,則不同的類加載,根據判定兩個類相等的法則,會導緻存在不同版本的String類,會導緻不相容問題。
核心代碼:
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) {
// [調用父類加載器的loadClass方法,實作了自底向上的檢查類是否被加載的功能]
c = parent.loadClass(name, false);
} else {
// [父類加載器為null,也就是去調用BootClassLoader加載]
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
long t1 = System.nanoTime();
// [調用目前類加載器findClass方法實作了的自頂向下的類加載功能:ExtClassLoader.findClass(name) -> AppClassLoader.findClass(name) -> CustomClassLoader.findClass(name)]
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;
}
}