##默認的三個類加載器 Java默認是有三個ClassLoader,按層次關係從上到下依次是: - Bootstrap ClassLoader - Ext ClassLoader - System ClassLoader Bootstrap ClassLoader是最頂層的ClassLoader,它比較特殊,是用C++編寫集成在JVM中的,是JVM啓動的時候用來加載一些核心類的,好比:`rt.jar`,`resources.jar`,`charsets.jar`,`jce.jar`等,能夠運行下面代碼看都有哪些: ``` URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs(); for (int i = 0; i < urls.length; i++) { System.out.println(urls[i].toExternalForm()); } ``` 其他兩個ClassLoader都是繼承自`ClassLoader`這個類。Java的類加載採用了一種叫作「雙親委託」的方式(稍後解釋),因此除了`Bootstrap ClassLoader`其他的ClassLoader都有一個「父」類加載器, 不是經過繼承,而是一種包含的關係。 ``` //ClassLoader.java public abstract class ClassLoader { ... // The parent class loader for delegation private ClassLoader parent; ... ``` ##「雙親委託」 所謂「雙親委託」就是當加載一個類的時候會先委託給父類加載器去加載,當父類加載器沒法加載的時候再嘗試本身去加載,因此整個類的加載是「自上而下」的,若是都沒有加載到則拋出`ClassNotFoundException`異常。 上面提到Bootstrap ClassLoader是最頂層的類加載器,實際上Ext ClassLoader和System ClassLoader就是一開始被它加載的。 Ext ClassLoader稱爲擴展類加載器,負責加載Java的擴展類庫,默認加載JAVA_HOME/jre/lib/ext/目錄下的全部的jar(包括本身手動放進去的jar包)。 System ClassLoader叫作系統類加載器,負責加載應用程序classpath目錄下的全部jar和class文件,包括咱們平時運行jar包指定cp參數下的jar包。 運行下面的代碼能夠驗證上面內容: ``` ClassLoader loader = Debug.class.getClassLoader(); while(loader != null) { System.out.println(loader); loader = loader.getParent(); } System.out.println(loader); ``` ##「雙親委託」的做用 之因此採用「雙親委託」這種方式主要是爲了安全性,避免用戶本身編寫的類動態替換Java的一些核心類,好比String,同時也避免了重複加載,由於JVM中區分不一樣類,不單單是根據類名,相同的class文件被不一樣的ClassLoader加載就是不一樣的兩個類,若是相互轉型的話會拋`java.lang.ClassCaseException`. ##自定義類加載器 除了上面說的三種默認的類加載器,用戶能夠經過繼承`ClassLoader`類來建立自定義的類加載器,之因此須要自定義類加載器是由於有時候咱們須要經過一些特殊的途徑建立類,好比網絡。 至於自定義類加載器是如何發揮做用的,`ClassLoader`類的loadClass方法已經把算法定義了: ``` 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; } ``` >1. Invoke `findLoadedClass(String)` to check if the class has already been loaded. >2. Invoke the loadClass method on the parent class loader. If the parent is null the class loader built-in to the virtual machine is used, instead. >3. Invoke the `findClass(String)` method to find the class. 看上面的Javadoc能夠知道,自定義的類加載器只要重載`findClass`就行了。 ##Context ClassLoader 首先Java中ClassLoader就上面提到的四種,`Bootstrap ClassLoader`,`Ext ClassLoader`,`System ClassLoader`以及用戶自定義的,因此`Context ClassLoader`並非一種新的類加載器,確定是這四種的一種。 首先關於類的加載補充一點就是若是類A是被一個加載器加載的,那麼類A中引用的B也是由這個加載器加載的(若是B尚未被加載的話),一般狀況下就是類B必須在類A的classpath下。 可是考慮多線程環境下不一樣的對象多是由不一樣的ClassLoader加載的,那麼當一個由ClassLoaderC加載的對象A從一個線程被傳到另外一個線程ThreadB中,而ThreadB是由ClassLoaderD加載的,這時候若是A想獲取除了本身的classpath之外的資源的話,它就能夠經過`Thread.currentThread().getContextClassLoader()`來獲取線程上下文的ClassLoader了,通常就是ClassLoaderD了,能夠經過`Thread.currentThread().setContextClassLoader(ClassLoader)`來顯示的設置。 ##爲何要有Context ClassLoader 之因此有Context ClassLoader是由於Java的這種「雙親委託」機制是有侷限性的: - 舉網上的一個例子: > JNDI爲例,JNDI的類是由bootstrap ClassLoader從rt.jar中間載入的,可是JNDI具體的核心驅動是由正式的實現提供的,而且一般會處於-cp參數之下(注:也就是默認的System ClassLoader管理),這就要求bootstartp ClassLoader去載入只有SystemClassLoader可見的類,正常的邏輯就沒辦法處理。怎麼辦呢?parent能夠經過得到當前調用Thread的方法得到調用線程的>Context ClassLoder 來載入類。 - 我上面提到的加載資源的例子。 `Contex ClassLoader`提供了一個突破這種機制的後門。 Context ClassLoader通常在一些框架代碼中用的比較多,平時寫代碼的時候用類的ClassLoader就能夠了。 ##參考連接 [http://stackoverflow.com/questions/1771679/difference-between-threads-context-class-loader-and-normal-classloader][1] [1]: http://stackoverflow.com/questions/1771679/difference-between-threads-context-class-loader-and-normal-classloader