類加載器是負責將可能是網絡上、也可能是磁盤上的class文件加載到內存中。並為其生成對應的java.lang.class對象。一旦一個類被載入JVM了,同一個類就不會被再次加載。那么怎樣才算是同一個類?在JAVA中一個類用其全限定類名(包名和類名)作為其唯一標識,但是在JVM中,一個類用其全限定類名和其類加載器作為其唯一標識。也就是說,在JAVA中的同一個類,如果用不同的類加載器加載,則生成的class對象認為是不同的。
當JVM啟動時,會形成由三個類加載器組成的初始類加載器層次結構
1、啟動類加載器BootstrapClassLoader:
是嵌在JVM內核中的加載器,該加載器是用C++語言寫的,主要負載加載JAVA_HOME/lib下的類庫,啟動類加載器無法被應用程序直接使用。
2、擴展類加載器Extension ClassLoader:
該加載器器是用JAVA編寫,且它的父類加載器是Bootstrap,是由sun.misc.Launcher$ExtClassLoader實現的,主要加載JAVA_HOME/lib/ext目錄中的類庫。開發者可以這幾使用擴展類加載器。
我們知道java中系統屬性java.ext.dirs指定的目錄由ExtClassLoader加載器加載,如果程序中沒有指定該系統屬性(-Djava.ext.dirs=sss/lib)那么該加載器默認加載$JAVA_HOME/lib/ext目錄下的所有jar文件,通過程序來看下系統變量java.ext.dirs所指定的路徑:
public class Test { public static void main(String[] args) { System.out.println(System.getProperty("java.ext.dirs")); } }
執行結果:
C:\Program Files (x86)\Java\jdk1.6.0_43\jre\lib\ext;C:\Windows\Sun\Java\lib\ext
3、系統類加載器App ClassLoader:
系統類加載器,也稱為應用程序類加載器,負責加載應用程序classpath目錄下的所有jar和class文件。它的父加載器為Ext ClassLoader。
public class Test { public static void main(String[] args) { System.out.println(ClassLoader.getSystemClassLoader()); } }
執行結果:
sun.misc.Launcher$AppClassLoader@addbf1
程序中的方法是返回委托的系統類加載器,通過執行結果,可以知道,系統類加載器是通過sun.misc.Launcher$AppClassLoader實現的。
上述三種類加載器的層次關系如下:

注意:類加載器的體系並不是“繼承”體系,而是委派體系,大多數類加載器首先會到自己的parent中查找類或者資源,如果找不到才會到自己本地查找。類加載器的委托行為動機是為了避免相同的類被加載多次。
我們可以通過程序來驗證下:
public static void main(String[] args) { System.out.println(ClassLoader.getSystemClassLoader().getParent()); }
執行結果:
sun.misc.Launcher$ExtClassLoader@42e816
在這里可以看到,Application ClassLoader的父加載器確實是ExtClassLoader。
我們在往上走一層,如果猜想沒錯的話,ExtClassLoader的父加載器應該是BootStrap ClassLoader
public static void main(String[] args) { System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent()); }
執行結果:
null
這里不是說ExtClassLoader沒有父加載器,而是因為Bootstrap ClassLoader使用C++寫的。

UML類圖:

我們解讀源碼來看下這些類的繼承關系
AppClassLoader與ExtClassLoader都是Lancher的內部類:
static class ExtClassLoader extends URLClassLoader
static class AppClassLoader extends URLClassLoader
public class URLClassLoader extends SecureClassLoader
public class SecureClassLoader extends ClassLoader
public abstract class ClassLoader
我們有這樣一個結論,除了啟動類加載器Bootstrap ClassLoader,其他的類加載器都是ClassLoader的子類。
我們來反編譯看下rt.jar,在sun.misc.Launcher$AppClassLoader路徑中,看下AppClassLoader的源碼:
1 static class AppClassLoader extends URLClassLoader 2 { 3 public static ClassLoader getAppClassLoader(ClassLoader paramClassLoader) 4 throws IOException 5 { 6 String str = System.getProperty("java.class.path"); 7 File[] arrayOfFile = (str == null) ? new File[0] : Launcher.access$200(str); 8 9 return ((AppClassLoader)AccessController.doPrivileged(new PrivilegedAction(str, arrayOfFile, paramClassLoader) 10 { 11 public Object run() { 12 URL[] arrayOfURL = (this.val$s == null) ? new URL[0] : Launcher.access$300(this.val$path); 13 14 return new Launcher.AppClassLoader(arrayOfURL, this.val$extcl); 15 } 16 })); 17 }
1、在第6行代碼中可以看到,系統類加載器只能加載java.class.path路徑下的class文件。我們通過程序看下java.class.path指定的路徑
1 public class Test 2 { 3 public static void main(String[] args) 4 { 5 System.out.println(System.getProperty("java.class.path")); 6 } 7 }
執行結果:
如果是JAVA工程:
F:\workSpace\test\bin
如果是JAVAWEB工程:
F:\workSpace\study\WebRoot\WEB-INF\classes
雙親委派模型
如果一個類加載器收到了一個類加載請求,它不會自己去嘗試加載這個類,而是把這個請求轉交給父類加載器去完成。每一個層次的類加載器都是如此。因此所有的類加載請求都應該傳遞到最頂層的啟動類加載器中,只有到父類加載器反饋自己無法完成這個加載請求(在它的搜索范圍沒有找到這個類)時,子類加載器才會嘗試自己去加載。委派的好處就是避免有些類被重復加載。
雙親委派的實現比較簡單,我們來看下源碼:
1 protected synchronized Class<?> loadClass(String paramString, boolean paramBoolean) 2 throws ClassNotFoundException 3 {
//檢查是否被加載過 4 Class localClass = findLoadedClass(paramString);
//如果沒有加載,則調用父類加載器 5 if (localClass == null) { 6 try {
//父類加載器不為空 7 if (this.parent != null) 8 localClass = this.parent.loadClass(paramString, false); 9 else {
//父類加載器為空,則使用啟動類加載器 10 localClass = findBootstrapClass0(paramString); 11 } 12 } 13 catch (ClassNotFoundException localClassNotFoundException) 14 {
//如果父類加載失敗,則使用自己的findClass方法進行加載 15 localClass = findClass(paramString); 16 } 17 } 18 if (paramBoolean) { 19 resolveClass(localClass); 20 } 21 return localClass; 22 }
先檢查是否已經被加載過,若沒有加載則調用父類加載器的loadClass方法,若父類加載器不存在,則使用啟動類加載器。如果父類加載器加載失敗,則拋出異常之后看,再調用自己的findClass方法進行加載。
