在上一篇JVM類加載機制詳解(一)JVM類加載過程中說到,類加載機制的第一個階段加載作的工做有:java
一、經過一個類的全限定名(包名與類名)來獲取定義此類的二進制字節流(Class文件)。而獲取的方式,能夠經過jar包、war包、網絡中獲取、JSP文件生成等方式。網絡
二、將這個字節流所表明的靜態存儲結構轉化爲方法區的運行時數據結構。這裏只是轉化了數據結構,並未合併數據。(方法區就是用來存放已被加載的類信息,常量,靜態變量,編譯後的代碼的運行時內存區域)數據結構
三、在內存中生成一個表明這個類的java.lang.Class對象,做爲方法區這個類的各類數據的訪問入口。這個Class對象並無規定是在Java堆內存中,它比較特殊,雖爲對象,但存放在方法區中。app
其中,實現第一個工做的代碼塊就被稱爲「類加載器」。測試
類加載器的做用不只僅是實現類的加載,它還與類的的「相等」斷定有關,關係着Java「相等」斷定方法的返回結果,只有在知足以下三個類「相等」斷定條件,才能斷定兩個類相等。spa
一、兩個類來自同一個Class文件.net
二、兩個類是由同一個虛擬機加載3d
三、兩個類是由同一個類加載器加載code
Java「相等」斷定相關方法:對象
一、判斷兩個實例對象的引用是否指向內存中同一個實例對象,使用 Class對象的equals()方法,obj1.equals(obj2);
二、判斷實例對象是否爲某個類、接口或其子類、子接口的實例對象,使用Class對象的isInstance()方法,class.isInstance(obj);
三、判斷實例對象是否爲某個類、接口的實例,使用instanceof關鍵字,obj instanceof class;
四、判斷一個類是否爲另外一個類自己或其子類、子接口,可使用Class對象的isAssignableFrom()方法,class1.isAssignableFrom(class2)。
JVM類加載器分類詳解:
一、Bootstrap ClassLoader:啓動類加載器,也叫根類加載器,它負責加載Java的核心類庫,加載如(%JAVA_HOME%/lib)目錄下的rt.jar(包含System、String這樣的核心類)這樣的核心類庫。根類加載器很是特殊,它不是java.lang.ClassLoader的子類,它是JVM自身內部由C/C++實現的,並非Java實現的。
二、Extension ClassLoader:擴展類加載器,它負責加載擴展目錄(%JAVA_HOME%/jre/lib/ext)下的jar包,用戶能夠把本身開發的類打包成jar包放在這個目錄下便可擴展核心類之外的新功能。
三、System ClassLoader\APP ClassLoader:系統類加載器或稱爲應用程序類加載器,是加載CLASSPATH環境變量所指定的jar包與類路徑。通常來講,用戶自定義的類就是由APP ClassLoader加載的。
各類類加載器間關係:以組合關係複用父類加載器的父子關係,注意,這裏的父子關係並非以繼承關係實現的。
//驗證類加載器與類加載器間的父子關係
public static void main(String[] args) throws Exception{
//獲取系統/應用類加載器
ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
System.out.println("系統/應用類加載器:" + appClassLoader);
//獲取系統/應用類加載器的父類加載器,獲得擴展類加載器
ClassLoader extcClassLoader = appClassLoader.getParent();
System.out.println("擴展類加載器" + extcClassLoader);
System.out.println("擴展類加載器的加載路徑:" + System.getProperty("java.ext.dirs"));
//獲取擴展類加載器的父加載器,但因根類加載器並非用Java實現的因此不能獲取
System.out.println("擴展類的父類加載器:" + extcClassLoader.getParent());
}
}
類加載器的雙親委派加載機制(重點):當一個類收到了類加載請求,他首先不會嘗試本身去加載這個類,而是把這個請求委派給父類去完成,每個層次類加載器都是如此,所以全部的加載請求都應該傳送到啓動類加載其中,只有當父類加載器反饋本身沒法完成這個請求的時候(在它的加載路徑下沒有找到所需加載的Class),子類加載器纔會嘗試本身去加載。
這個過程以下圖標號過程所示:
雙親委派模型的源碼實現:
主要體如今ClassLoader的loadClass()方法中,思路很簡單:先檢查是否已經被加載過,若沒有加載則調用父類加載器的loadClass()方法,若父類加載器爲空則默認使用啓動類加載器做爲父類加載器。若是父類加載器加載失敗,拋出ClassNotFoundException異常後,調用本身的findClass()方法進行加載。
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
protected synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
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.
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
下面看一個簡單的雙親委派模型代碼實例驗證:
public class ClassLoaderTest {
public static void main(String[] args){
//輸出ClassLoaderText的類加載器名稱
System.out.println("ClassLoaderText類的加載器的名稱:"+ClassLoaderTest.class.getClassLoader().getClass().getName());
System.out.println("System類的加載器的名稱:"+System.class.getClassLoader());
System.out.println("List類的加載器的名稱:"+List.class.getClassLoader());
ClassLoader cl = ClassLoaderTest.class.getClassLoader();
while(cl != null){
System.out.print(cl.getClass().getName()+"->");
cl = cl.getParent();
}
System.out.println(cl);
}
輸出結果爲:
解釋一下:
一、ClassLoaderTest類是用戶定義的類,位於CLASSPATH下,由系統/應用程序類加載器加載。
二、System類與List類都屬於Java核心類,由祖先類啓動類加載器加載,而啓動類加載器是在JVM內部經過C/C++實現的,並非Java,天然也就不能繼承ClassLoader類,天然就不能輸出其名稱。
三、而箭頭項表明的就是類加載的流程,層級委託,從祖先類加載器開始,直到系統/應用程序類加載器處才被加載。
那麼咱們作個測試,把類打成jar包,拷貝入%JAVA_HOME%/jre/lib/ext目錄下,再次運行ClassLoaderTest類
解釋一下,由於類的Jar包放到了ExtClassLoader的加載目錄下,因此在根目錄找不到相應類後,在ExtClassLoader處就完成了類加載,而忽略了APPClassLoader階段。