目錄
一、類加載器
還記得類加載機制嗎?類加載機制的各階段是加載、連接(驗證、準備、解析)、初始化、使用、卸載??蓞⒖忌掀恼拢?a href="http://www.itdecent.cn/p/4f5b11c11558" target="_blank">JVM學(xué)習(xí)(一):Java類的加載機制 里有詳細說明。
1. 什么是類加載器?
把類加載階段中的"通過一個類的全限定名來獲取描述此類的二進制字節(jié)流"這個動作放到Java虛擬機外部去實現(xiàn),以便讓應(yīng)用程序自己決定如何去獲取所需要的類。實現(xiàn)這個動作的代碼模塊稱為“類加載器”。
2. 類與類加載器
類加載器雖然只用于實現(xiàn)類的加載動作,但它在Java程序中祈禱的作用卻遠遠不限于類加載階段。
對于任意一個類,都需要由加載它的類加載器和這個類本身一同確立啊Java虛擬機中的唯一性。每一個類加載器都擁有一個獨立的類名稱空間。
比較兩個類是否“相等“,只有在這兩個類是由同一個類加載器加載的前提下才由意義;否則,即使兩個類來源于同一個Class文件,被同一個虛擬機加載,只要加載他們的類加載器不同,那么這兩個類就必定不相等。
(這里指的“相等”,包括代表類的Class對象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回結(jié)果,也包括關(guān)鍵字instanceof做對象所屬關(guān)系判定情況。)
- 代碼演示:
public class ClassLoaderTest { public static void main(String[] args) throws Exception { ClassLoader loader = new ClassLoader() { @Override public Class<?> loadClass(String name) throws ClassNotFoundException { try { String fileName = name.lastIndexOf("." + 1) + ".class"; InputStream inputStream = this.getClass().getResourceAsStream(fileName); if (inputStream == null) { return super.loadClass(name); } byte[] bytes = new byte[inputStream.available()]; inputStream.read(bytes); return defineClass(name, bytes, 0, bytes.length); } catch (IOException e) { e.printStackTrace(); throw new ClassNotFoundException(name); } } }; Object obj = loader.loadClass("com.jx.Test1").newInstance(); System.out.println(obj.getClass()); // 打印類名稱 System.out.println(obj instanceof com.jx.Test1); // 打印 比較obj對象是否是com.jx.Test1類 } } - 運行結(jié)果:
class com.jx.Test1 false
從示例代碼Object obj = loader.loadClass("com.jx.Test1").newInstance(); System.out.println(obj.getClass());打印結(jié)果是com.jx.Test1,說明通過自定義的類加載器 加載并實例的對象確實是Tes1的類;
但代碼System.out.println(obj instanceof com.jx.Test1);運行輸出的結(jié)果是false,這是因為在JVM虛擬機中存在另外一個類加載器加載了。
雖然都是來自同一個Class文件,但因為是兩個獨立的類加載器加載出來的類,在做對象所屬類型檢測時結(jié)果是false。
二、類加載器分類

-
類加載器可以分為:
- 啟動類加載器(Bootstrap ClassLoader)
- 擴展類加載器(Extension ClassLoader)
- 應(yīng)用程序類加載器(Application ClassLoader)
- 自定義類加載器(USer ClassLoader)
他們的關(guān)系是:
自定義類加載器的父類是應(yīng)用程序類加載器;
應(yīng)用程序類加載器的父類是擴展類加載器;
啟動類加載器嚴格意義上不是擴展類加載器的父類,抽象維度可以理解為父類。
1. 啟動類加載器(Bootstrap ClassLoader)
啟動類加載器(Bootstrap ClassLoader) 是最頂層的類加載器,主要加載核心類庫。
- 加載路徑
\jdk\jre\lib下的rt.jar、resource.jar、charsets.jar和class等。 - 啟動類架子啊其是無法被Java程序直接引用的。
Bootstrap ClassLoader不繼承自ClassLoader,因為它不是一個普通的Java類,底層是由C++編寫嵌入到JVM內(nèi)核中;
當(dāng)JVM啟動后 Bootstrap ClassLoader也隨著啟動,賦值加載完核心類庫后,并構(gòu)造Extension ClassLoader和Application ClassLoader。
如圖:
啟動類加載器加載路徑
另外,可以通過啟動JVM時指定-Xbootclasspath路徑來改變Bootstrap ClassLoader的加載目錄。
2. 擴展類加載器(Extension ClassLoader)
擴展類加載器(Extension ClassLoader):這個類加載器由sun.misc.Luancher&ExtClassLoader實現(xiàn)。
- 負責(zé)加載
\jre\lib\ext目錄下的jar包和class文件. - 或者由
java.ext.dirs系統(tǒng)變量指定路徑中的所有類庫(如javax.開頭的類),開發(fā)者可以直接使用擴展類加載器。
如圖:
擴展類加載器加載路徑
3. 應(yīng)用程序類加載器(Application ClassLoader)
應(yīng)用程序類加載器(Application ClassLoader):是由sun.misc.Launcher&ApplicationClassLoaer實現(xiàn)。
- Application ClassLoader是負責(zé)加載
用戶類路徑上所指定的類庫,開發(fā)者可以直接使用這個類加載器,如果應(yīng)用程序沒有自定義過自己的類加載器,一般情況下就是程序默認的類加載器。
4. 自定義類加載器(User ClassLoader)
**自定義類加載器(User ClassLoader)
**:一般是繼承ClassLoader,重寫findClass方法。
因為JVM自帶的ClassLoader只會從本地文件系統(tǒng)加載標準的Java class文件,因此編寫自定義類加載器可以做到:
- 在執(zhí)行非自信代碼之前,自動驗證數(shù)字簽名。
- 動態(tài)地創(chuàng)建符合用戶特定需要的定制化構(gòu)建類。
- 從特定的場所取得Java class,例如數(shù)據(jù)庫和網(wǎng)絡(luò)中。
5. 類加載器體系結(jié)構(gòu)(雙親委派模型)

關(guān)于類加載器的加載過程:
-
- 稱為緩存查找環(huán)節(jié)。第一步是先檢查類加載器中是否已經(jīng)緩存加載了對應(yīng)的類。 其中又分為:
- ① 若存在自定義類加載器,則先檢查自身緩存中是否存在;如果存在則取到。
- ② 如果自定義緩存不存在,委托父類查找,也就是應(yīng)用程序類加載器。
應(yīng)用程序類加載器同樣也先檢查緩存中是否存在,如果存在則取到。 - ③ 如果應(yīng)用緩存不存在,則委托它的父類,既是擴展類加載器。
擴展類加載器同樣也會先檢查緩存中是否存在,如果存在則取到。 - ④ 如果擴展類加載器緩存也不存在,則調(diào)用啟動類加載器查找。
啟動類加載器也是先檢查是否已經(jīng)加載,如果加載,則取到。如果未加載,則進入加載環(huán)節(jié)。
-
- 加載環(huán)節(jié)。第二步,在所有類加載器通過緩存都找不到時,則進入類加載環(huán)節(jié)。類加載環(huán)節(jié)可分為:
- ① 啟動類加載器。啟動類加載器在緩存找不到后,會根據(jù)它的路徑范圍
jre\lib\rt.jar查找加載對應(yīng)類。如果成功加載,則返回;如果不成功,則進入②。 - ② 擴展類加載器。擴展類加載器在收到啟動類加載器未成功的情況下,會根據(jù)它的路徑訪問
jre\lib\ext\*.jar查找加載對應(yīng)類。如果成功加載,則返回;如果不成功,則進入③。 - ③ 應(yīng)用程序類加載器。應(yīng)用程序類加載器收到擴展類加載器不成功的情況下,會根據(jù)它的路勁訪問
ClassPath查找加載對應(yīng)的類。如果成功加載,則返回;如果不成功,則進入④。 - ④ 自定義類加載器。如果應(yīng)用程序類加載器在收到應(yīng)用程序類加載器不成功的情況下,會根據(jù)它自定義的路徑訪問查找加載對應(yīng)的類。如果成功加載,則返回;如果不從,則拋出ClassNotFoundExcepiton異常。
上面的流程又可以稱為是雙親委派模型。
雙親委派模型


6. 類的加載方式
類的加載方式有三種:
-
命令行啟動應(yīng)用的時候由JVM初始化加載。
用一張圖即可說明。請見下圖:
main方法JVM配置
-
-
- 通過
Class.forName()方法動態(tài)加載。
- ① 我們先看測試示例代碼:
public class TestClassLoader { public static void main(String[] args) throws ClassNotFoundException { Class.forName("com.jx.Test1");//直接通過Class.forName()來加載類 } }- ② 接著跟進去查看Class.for()方法的實現(xiàn)。
public static Class<?> forName(String className) throws ClassNotFoundException { Class<?> caller = Reflection.getCallerClass(); return forName0(className, true, ClassLoader.getClassLoader(caller), caller); // 這里會調(diào)用ClassLoader.getClassLoader()方法獲得該類的類加載器對象 }- ③ 再跟進forName0()方法,是一個native方法。
private static native Class<?> forName0(String name, boolean initialize, ClassLoader loader, Class<?> caller) throws ClassNotFoundException;- ④ 上面的native forName0()方法會調(diào)用類加載器的
ClassLoader.loadclass()方法。
Class.forName()方法調(diào)用棧
通過上面調(diào)用棧會發(fā)現(xiàn)
Class.forName()方法本質(zhì)上最后會調(diào)用ClassLoader.loadClass()方法。 - 通過
-
- 通過
ClassLoader.loadClass()方法動態(tài)加載。
直接上ClassLoader.loadClass()方法代碼,代碼的注釋已經(jīng)說明了很清楚了。
public Class<?> loadClass(String name) throws ClassNotFoundException { return loadClass(name, false); } protected Class<?> loadClass(String name, boolean resolve) //resolve字段表示是否進行【連接】階段處理 throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded // 首先,判斷該類是否已經(jīng)加載過了。 Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { //如果父類存在 // 如果未加載過,則委派給父類進行加載。 c = parent.loadClass(name, false); } else { // 如果父類不存在,則交給BootstrapClassLoader來加載。 什么時候父類不存在呢?其實就是ExtClassLoader不存在父類的情況。 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader // 如果父類通過緩存+加載都無法找到,并拋出ClassNotFoundException異常時,則捕獲異常但不處理。 } 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; } }代碼中有幾個關(guān)鍵調(diào)用需要注意:
- ①
Class<?> c = findLoadedClass(name)通過緩存查找判斷是否存在該類。
進一步查看該方法實現(xiàn),又調(diào)用了native findLoadedClass0方法。protected final Class<?> findLoadedClass(String name) { if (!checkName(name)) return null; return findLoadedClass0(name); } private native final Class<?> findLoadedClass0(String name); - ② 當(dāng)parent != null時,
c = parent.loadClass(name, false);。如果父類不為空,則委派給父類的loadClass()方法執(zhí)行。
當(dāng) parent == null是,c = findBootstrapClassOrNull(name);父類如果為空時,則委派給BootstrapClassLoader來查找。
這里就是雙親委派模型出現(xiàn)了。 - ③ 當(dāng)在經(jīng)過父類們緩存查找和加載后,仍然未找到該類,則本加載器會親自進行查找
c = findClass(name);。這個方法很關(guān)鍵。
通常情況下,我們自定義的用戶類加載器通過繼承ClassLoader抽象類后,重寫protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); }findClass()方法是比較的靠譜的。
到這里已經(jīng)把雙親委派模型講解了,還順帶講解了自定義類加載器。
- 通過
三、ClassLoader代碼解讀-雙親委派模型
通過上面的《通過ClassLoader.loadClass()方法動態(tài)加載》已經(jīng)將雙親委派模型已經(jīng)詳細講解了。
部分補充請查看:
JVM學(xué)習(xí)(二)續(xù)1-ClassLoader代碼解讀-雙親委派模型
四、自定義類加載器詳解
請參考另外一篇文章中有詳細講解。
JVM學(xué)習(xí)(二)續(xù)2-自定義類加載器詳解



