標籤:
前文:Java中的所有類,必須被裝載到jvm中才能運行,這個裝載工作是由jvm中的類裝載器完成的。類裝載器所做的工作實質是把類檔案從硬碟讀取到jvm運行記憶體中,或者從網路中讀取到jvm運行記憶體中JVM在載入類的時候,都是通過ClassLoader的loadClass()方法來載入class的。 例如:
public class TestClassLoader { public static void main(String[] args) { System.out.println(TestClassLoader.class.getClassLoader()); }}
運行結果:
[email protected]
但是ClassLoader是怎樣載入class的呢?
一、java ClassLoader體系
Java預設是有三個ClassLoader,按層次關係從上到下依次是:
- Bootstrap ClassLoader
- ExtClassLoader
- AppClassLoader
Bootstrap ClassLoader是最頂層的ClassLoader,比較特殊,是用C++編寫整合在JVM中的,是JVM啟動的時候用來載入一些核心類的,比如:rt.jar,resources.jar,charsets.jar,jce.jar等
import java.net.URL;public class TestClassLoader { public static void main(String[] args) { URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs(); for (int i = 0; i < urls.length; i++) { System.out.println(urls[i].toExternalForm()); } }}
運行結果為:
file:/C:/Program%20Files/Java/jre1.8.0_73/lib/resources.jarfile:/C:/Program%20Files/Java/jre1.8.0_73/lib/rt.jarfile:/C:/Program%20Files/Java/jre1.8.0_73/lib/sunrsasign.jarfile:/C:/Program%20Files/Java/jre1.8.0_73/lib/jsse.jarfile:/C:/Program%20Files/Java/jre1.8.0_73/lib/jce.jarfile:/C:/Program%20Files/Java/jre1.8.0_73/lib/charsets.jarfile:/C:/Program%20Files/Java/jre1.8.0_73/lib/jfr.jarfile:/C:/Program%20Files/Java/jre1.8.0_73/classes
Bootstrap ClassLoader載入的類全都是java自有的核心類。
ExtClassLoader、AppClassLoader都是繼承自Bootstrap ClassLoader。
ExtClassLoader是用來載入擴充類載入器,負責載入Java的擴充類庫,預設載入JAVA_HOME/jre/lib/ext/目錄下的所有的jar
AppClassLoader叫做系統類別載入器,負責載入應用程式classpath目錄下的所有jar和class檔案,包括我們平時運行jar包指定cp參數下的jar包
我們問題是,雖然有三個類載入器,但是jvm怎樣知道一個java 類是哪個ClassLoader來載入的呢,使用什麼規則或者演算法? 二、ClassLoader雙親委派模式通過查看java.lang.ClassLoader(所有的類載入器都是繼承該類)的public Class<?> loadClass(String name) throws ClassNotFoundException ;又調用了protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException;
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 { //如果父載入器不為空白,使用父載入器載入class if (parent != null) { c = parent.loadClass(name, false); } else { //如果父載入器也為空白,使用bootstarpClassLoader載入 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; } }
這時問題是,parent是什麼?
public abstract class ClassLoader { // The parent class loader for delegation private final ClassLoader parent; ...}
原來ClassLoader內部使用了父載入器,這就是雙親委託模式。
:
當前類載入器先不載入class,委託給父載入器載入class,如果父載入器沒有載入成功,本類載入器再載入class。 可以使用下面代碼測試下:
public class TestClassLoader { public static void main(String[] args) { ClassLoader loader = MyAppClassLoader.class.getClassLoader(); while(loader != null) { System.out.println(loader); loader = loader.getParent(); } System.out.println(loader); }}
運行結果為:
[email protected][email protected]null
這樣做的好處是:為了安全性,避免使用者自己編寫的類動態替換Java的一些核心類,比如String,同時也避免了重複載入,因為JVM中區分不同類,不僅僅是根據類名,相同的class檔案被不同的ClassLoader載入就是不同的兩個類,如果相互轉型的話會拋java.lang.ClassCaseException 三、自訂ClassLoader通常我們不需要自己實現ClassLoader,但是在一些特殊的情境需要自訂ClassLoader,比如tomcat載入webApp下面的Class,或者一些RPC架構,我們需要繼承java.lang.ClassLoader ,比如:
public class MyAppClassLoader extends ClassLoader{ @Override public Class<?> findClass(String name) throws ClassNotFoundException { //從特殊路徑下讀取class,或者從網路中讀取class,不管咋樣,實現自己的方法,返回載入class //... }}
四、ContextLoader當前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是因為Java的這種“雙親委託”機制是有局限性的:
比如: JNDI為例,JNDI的類是由bootstrap ClassLoader從rt.jar中間載入的,但是JNDI具體的核心驅動是由正式的實現提供的,並且通常會處於-cp參數之下(註:也就是預設的System ClassLoader管理),這就要求bootstartp ClassLoader去載入只有SystemClassLoader可見的類,正常的邏輯就沒辦法處理。怎麼辦呢?parent可以通過獲得當前調用Thread的方法獲得調用線程的>Context ClassLoder 來載入類。
TODO ,contextLoader會繼續補充,待完善。
ClassLoader原理分析