Java的類加載器在sun.misc.Launcher中初始化。
public Launcher() {
ExtClassLoader localExtClassLoader;
try {
localExtClassLoader = ExtClassLoader.getExtClassLoader();
}
catch(IOException localIOException1) {
throw new InternalError("Could not create extension class loader", localIOException1);
}
try {
this.loader = AppClassLoader.getAppClassLoader(localExtClassLoader);
}
catch(IOException localIOException2) {
throw new InternalError("Could not create application class loader", localIOException2);
}
Thread.currentThread().setContextClassLoader(this.loader);
String str = System.getProperty("java.security.manager");
if(str != null) {
SecurityManager localSecurityManager = null;
if(( "".equals(str) ) || ( "default".equals(str) ))
localSecurityManager = new SecurityManager();
else
try {
localSecurityManager = (SecurityManager) this.loader.loadClass(str).newInstance();
}
catch(IllegalAccessException localIllegalAccessException) {}
catch(InstantiationException localInstantiationException) {}
catch(ClassNotFoundException localClassNotFoundException) {}
catch(ClassCastException localClassCastException) {}
if(localSecurityManager != null)
System.setSecurityManager(localSecurityManager);
else
throw new InternalError("Could not create SecurityManager: " + str);
}
}
ExtClassLoader通過ExtClassLoader.getExtClassLoader()初始化。
AppClassLoader通過AppClassLoader.getAppClassLoader(ExtClassLoader)初始化。
public static ExtClassLoader getExtClassLoader() throws IOException {
File[] arrayOfFile = getExtDirs();
try {
return( (ExtClassLoader) AccessController.doPrivileged(new PrivilegedExceptionAction(arrayOfFile) {
public Launcher.ExtClassLoader run() throws IOException {
int i = this.val$dirs.length;
for( int j = 0; j < i; ++j ) {
MetaIndex.registerDirectory(this.val$dirs[j]);
}
return new Launcher.ExtClassLoader(this.val$dirs);
}
}) );
}
catch(PrivilegedActionException localPrivilegedActionException) {
throw( (IOException) localPrivilegedActionException.getException() );
}
}
getExtDirs找到系統(tǒng)配置System.getProperty("java.ext.dirs")中的路徑下的所有文件,
讓類加載器加載這些文件。
public ExtClassLoader( File[] paramArrayOfFile ) throws IOException {
super(getExtURLs(paramArrayOfFile), null, Launcher.factory);
SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
}
ExtClassLoader初始化時將parent設(shè)置為null。
public static ClassLoader getAppClassLoader( ClassLoader paramClassLoader ) throws IOException {
String str = System.getProperty("java.class.path");
File[] arrayOfFile = ( str == null ) ? new File[0] : Launcher.access$200(str);
return( (ClassLoader) AccessController.doPrivileged(new PrivilegedAction(str, arrayOfFile, paramClassLoader) {
public Launcher.AppClassLoader run() {
URL[] arrayOfURL = ( this.val$s == null ) ? new URL[0] : Launcher.access$300(this.val$path);
return new Launcher.AppClassLoader(arrayOfURL, this.val$extcl);
}
}) );
}
AppClassLoader( URL[] paramArrayOfURL, ClassLoader paramClassLoader ) {
super(paramArrayOfURL, paramClassLoader, Launcher.factory);
this.ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);
this.ucp.initLookupCache(this);
}
getAppClassLoader從System.getProperty("java.class.path")處獲取要加載的文件,并在初始化時將類加載器的parent設(shè)置為ExtClassLoader。
類加載過程
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
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.
long t1 = System.nanoTime();
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;
}
}
該函數(shù)邏輯為:
if 該類已經(jīng)被當前加載器加載過
return;
else if 當前類加載器存在parent加載器
嘗試是否parent加載器加載
else if 不存在parent加載器器
嘗試使用BootstrapClassLoader加載器加載
if 父加載器無法加載該類
使用當前加載器加載。
因為AppClassLoader的parent為ExtClassLoader,而為ExtClassLoader的parent為null。依據(jù)上面的類加載過程,整個類的加載過程為

這種類加載的模式即為雙親委托模式,簡單一句話描述就是:
類優(yōu)先讓父加載器加載,父加載器無法加載后才會自己加載。
委托機制的意義
防止內(nèi)存中出現(xiàn)多份同樣的字節(jié)碼
比如兩個類A和類B都要加載System類:
如果不用委托而是自己加載自己的,那么加載器A就會加載一份System字節(jié)碼,然后加載器B又會加載一份System字節(jié)碼,這樣內(nèi)存中就出現(xiàn)了兩份System字節(jié)碼。
如果使用委托機制,會遞歸的向父類查找,也就是首選用Bootstrap嘗試加載,如果找不到再向下。這里的System就能在Bootstrap中找到然后加載,如果此時類B也要加載System,也從Bootstrap開始,此時Bootstrap發(fā)現(xiàn)已經(jīng)加載過了System那么直接返回內(nèi)存中的System即可而不需要重新加載,這樣內(nèi)存中就只有一份System的字節(jié)碼了。
當Java虛擬機要加載一個類時,到底派出哪個類加載器去加載呢?
- 首先當前線程的類加載器去加載線程中的第一個類(假設(shè)為類A)。
注:當前線程的類加載器可以通過Thread類的getContextClassLoader()獲得,也可以通過setContextClassLoader()自己設(shè)置類加載器。 - 如果類A中引用了類B,Java虛擬機將使用加載類A的類加載器去加載類B。
- 還可以直接調(diào)用ClassLoader.loadClass()方法來指定某個類加載器去加載某個類。
違背雙親委派模式
當然雙親委派模式并不是強制要求,在有些情況下會打破雙親委派模式。
如Tomcat加載類過程。

commonLoader:類庫可被Tomcat和所有的Web應用程序共同使用。
catalinaLoader:類庫可被Tomcat使用,對所有的Web應用程序都不可見。
sharedLoader:類庫可被所有的Web應用程序共同使用,但對Tomcat自己不可見。
webappclassLoader:每個Web應用程序單獨使用,對其他web應用不可見。
如果有10個Web應用程序都是用Spring來進行組織和管理的話,可以把Spring放到Common或Shared目錄下讓這些程序共享。Spring要對用戶程序的類進行管理,自然要能訪問到用戶程序的類,而用戶的程序顯然是放在/WebApp/WEB-INF目錄中的,那么被CommonClassLoader或SharedClassLoader加載的Spring如何訪問并不在其加載范圍內(nèi)的用戶程序呢?
因為Spring的jar包在common或shared目錄中,所以Spring的類加載器為commonLoader或者sharedLoader。而Spring中的注入的bean是在放在/WebApp/WEB-INF目錄中的,根據(jù) 如果類A中引用了類B,Java虛擬機將使用加載類A的類加載器去加載類B,在Spring中引用web應用的類時,需要commonLoader或者sharedLoader加載去加載,而commonLoader或sharedLoader是無法加載到/WebApp/WEB-INF下的類的,這時候就需要commonLoader或者sharedLoader加載器來調(diào)用子加載器webappclassLoader來加載/WebApp/WEB-INF目錄下的類,在實現(xiàn)時是使用線程上下文類加載器,可以實現(xiàn)父加載器對子加載器的逆向訪問。
熱部署
重新加載
ClassLoader中重要的方法
loadClass
ClassLoader.loadClass(...) 是ClassLoader的入口點。當一個類沒有指明用什么加載器加載的時候,JVM默認采用AppClassLoader加載器加載沒有加載過的class,調(diào)用的方法的入口就是loadClass(...)。如果一個class被自定義的ClassLoader加載,那么JVM也會調(diào)用這個自定義的ClassLoader.loadClass(...)方法來加載class內(nèi)部引用的一些別的class文件。重載這個方法,能實現(xiàn)自定義加載class的方式,拋棄雙親委托機制,但是即使不采用雙親委托機制,比如java.lang包中的相關(guān)類還是不能自定義一個同名的類來代替,主要因為JVM解析、驗證class的時候,會進行相關(guān)判斷。
defineClass
系統(tǒng)自帶的ClassLoader,默認加載程序的是AppClassLoader,ClassLoader加載一個class,最終調(diào)用的是defineClass(...)方法,這時候就在想是否可以重復調(diào)用defineClass(...)方法加載同一個類(或者修改過),最后發(fā)現(xiàn)調(diào)用多次的話會有相關(guān)錯誤:
...
java.lang.LinkageError
attempted duplicate class definition
...
所以一個class被一個ClassLoader實例加載過的話,就不能再被這個ClassLoader實例再次加載(這里的加載指的是,調(diào)用了defileClass(...)放方法,重新加載字節(jié)碼、解析、驗證。)。而系統(tǒng)默認的AppClassLoader加載器,他們內(nèi)部會緩存加載過的class,重新加載的話,就直接取緩存。所與對于熱加載的話,只能重新創(chuàng)建一個ClassLoader,然后再去加載已經(jīng)被加載過的class文件。
class卸載
在Java中class也是可以unload。JVM中class和Meta信息存放在PermGen space區(qū)域。如果加載的class文件很多,那么可能導致PermGen space區(qū)域空間溢出。引起:java.lang.OutOfMemoryErrorPermGen space. 對于有些Class我們可能只需要使用一次,就不再需要了,也可能我們修改了class文件,我們需要重新加載 newclass,那么oldclass就不再需要了。那么JVM怎么樣才能卸載Class呢。
JVM中的Class只有滿足以下三個條件,才能被GC回收,也就是該Class被卸載(unload):
- 該類所有的實例都已經(jīng)被GC。
- 加載該類的ClassLoader實例已經(jīng)被GC。
- 該類的java.lang.Class對象沒有在任何地方被引用。
GC的時機我們是不可控的,那么同樣的我們對于Class的卸載也是不可控的。