目錄
一裸弦、類加載器
還記得類加載機制嗎?類加載機制的各階段是加載作喘、連接(驗證理疙、準備、解析)泞坦、初始化窖贤、使用、卸載贰锁≡呶啵可參考上篇文章: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)核中;
當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初始化加載。
用一張圖即可說明破托。請見下圖:
-
-
- 通過
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()
方法谜洽。
通過上面調(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);
- ② 當parent != null時碍庵,
c = parent.loadClass(name, false);
材蛛。如果父類不為空圆到,則委派給父類的loadClass()方法執(zhí)行。
當 parent == null是卑吭,c = findBootstrapClassOrNull(name);
父類如果為空時芽淡,則委派給BootstrapClassLoader來查找。
這里就是雙親委派模型出現(xiàn)了豆赏。
- ③ 當在經(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-自定義類加載器詳解