深入理解Java中的類加載器原理
一、類加載機制
類加載器負責(zé)加載所有的類,系統(tǒng)為所有被載入內(nèi)存中的類生成一個java.lang.Class實例。一旦一個類被載入JVM中,同一個類就不會被再次載入了。在JVM中,一個類用其全限定類名和其類加載器作為唯一標識。
當JVM啟動時,會形成由三個類加載器組成的初始類加載器層次結(jié)構(gòu):
- Bootstrap ClassLoader:根類加載器,加載jre/lib/rt.jar、charset.jar等核心類,由C++實現(xiàn);
- Extension ClassLoader:擴展類加載器,加載擴展jar包,jre/lib/ext/*.jar,或由-Djava.ext.dirs指定;
- System ClassLoader:系統(tǒng)類加載器,加載classpath指定內(nèi)容。
Bootstrap ClassLoader被稱為引導(dǎo)(也成為原始或根)類加載器,它負責(zé)加載Java的核心類。在Sun的JVM中,當執(zhí)行java.exe命令時,使用-Xbootclasspath或-D選項指定sun.boot.class.path系統(tǒng)屬性值可以指定加載附加的類。
JVM的類加載機制主要有如下三種:
- 全盤負責(zé):所謂全盤負責(zé),就是當一個類加載器負責(zé)加載某個Class時,該Class所依賴的和引用的其他Class也將由該類加載器負責(zé)載入,除非顯示使用另外一個類加載器來載入。
- 父類委托:所謂父類委托,則是先讓parent(父)類加載器試圖加載該Class,只有在父類加載器無法加載該類時才嘗試從自己的類路徑中加載該類。
- 緩存機制:緩存機制將會保證所有加載過的Class都會被緩存,當程序中需要使用某個Class時,類加載器先從緩存區(qū)中搜尋該Class,只有當緩存區(qū)中不存在該Class對象時,系統(tǒng)才會讀取該類對應(yīng)的二進制數(shù)據(jù),并將其轉(zhuǎn)換成Class對象,存入到緩存區(qū)中。這就是為什么修改了Class后,必須重新啟動JVM,程序所做的修改才會生效的原因。
注:類加載器之間的父子關(guān)系并不是類繼承上的父子關(guān)系,這里的父子關(guān)系是類加載器實例之間的關(guān)系。
除了可以使用Java提供的類加載器之外,也可以通過繼承ClassLoader來實現(xiàn)自定義的類加載器。
JVM的根類加載器不是Java實現(xiàn)的,而且由于程序通常無須訪問根類加載器,因此訪問擴展類加載器的父類加載器時返回null。但實際上,擴展類加載器的父類加載器時根類加載器。
jdk8中,系統(tǒng)類加載器時AppClassLoader的實例,擴展類加載器是ExtClassLoader的實例。實際上,這兩個類都繼承URLClassLoader類0,因此也都是URLClassLoader類的實例。
類加載器加載Class大致要經(jīng)過如下8個步驟:
- 檢測此Class是否載入過(即在緩存區(qū)中是否有此Class),如果有則直接進入第8步,否則接著執(zhí)行第2步;
- 如果父類加載器不存在(如果沒有父類加載器,則要么parent一定是根類加載器,要么本身就是根類加載器),則直接執(zhí)行第3步;如果父類加載器存在,則繼續(xù)執(zhí)行第2步;
- 請求使用根類加載器來載入目標類,如果成功載入則跳到第9步,如果未成功若當前類加載器就是根類加載器則跳到第8步,否則接著執(zhí)行第4步;
- 請求使用擴展類加載器來載入目標類,如果成功載入則跳到第9步,如果未成功若當前類加載器就是擴展類加載器則跳到第8步,否則接著執(zhí)行第5步;
- 請求使用系統(tǒng)類加載器來載入目標類,如果成功載入則跳到第9步,如果未成功若當前類加載器就是系統(tǒng)類加載器則跳到第8步,否則接著執(zhí)行第6步;
- 當前類加載器嘗試尋找Class文件(從與此ClassLoader相關(guān)的類路徑中尋找),如果找到則執(zhí)行第7步,如果找不到則跳到第8步;
- 從文件中載入Class,成功載入后跳到第8步;
- 拋出ClassNotFoundException異常;
- 返回對應(yīng)的java.lang.Class對象。
注:其中,第6、7步允許重寫ClassLoader的findClass()方法來實現(xiàn)自己的載入策略,甚至重寫loadClass()方法來實現(xiàn)自己的載入過程。
下圖為類加載器加載Class過程的流程圖:
類加載器加載Class流程
二、雙親委派機制
JVM中類加載采用雙親委派機制,所謂雙親委派就是當一個類加載器收到類加載請求時他不會直接去加載目標類,而是把該請求委托給父類加載器去加載。只有當你父類加載器無法加載目標類時,才會交由當前類加載器加載目標類。
采用雙親委派機制,保證了安全性,避免核心類庫被自定義類加載器加載,也避免了類被重復(fù)加載。
下圖為雙親委派機制的流程圖。
三、自定義類加載器
JVM中除根類加載器之外的所有類加載器都是ClassLoader子類的實例,可以通過擴展ClassLoader的子類,并重寫該ClassLoader所包含的方法來實現(xiàn)自定義的類加載器。
ClassLoader中包含大量的protected方法——這些方法都可被子類重寫。
ClassLoader類有兩個關(guān)鍵方法:
- findClass(String name):根據(jù)指定名稱來查找類。
- loadClass(String name, boolean resolve):該方法為ClassLoader的入口點,根據(jù)指定名稱來加載類,系統(tǒng)就是調(diào)用ClassLoader的該方法來獲取指定類對應(yīng)的Class對象。
loadClass()方法的執(zhí)行步驟如下:
①用findLoadedClass(String)來檢查是否已經(jīng)加載類,如果已經(jīng)加載則直接返回。
②在父類加載器上調(diào)用loadClass()方法。如果父類加載器為null,則使用根類加載器來加載。
③調(diào)用findClass(String)方法查找類。
從上面步驟可以看出,重寫findClass()方法可以避免覆蓋默認類加載器的父類委托、緩沖機制兩種策略;如果重寫loadClass()方法則實現(xiàn)邏輯更為復(fù)雜。
ClassLoader里還有一個核心方法:Class defineClass(String name, byte[] b, int off, int len),該方法負責(zé)將指定類的字節(jié)碼文件(即Class文件)讀入字節(jié)數(shù)組byte[] b內(nèi),并把它轉(zhuǎn)換為Class對象,該字節(jié)碼文件可以來源于文件、網(wǎng)絡(luò)等、defineClass()方法管理JVM的許多復(fù)雜的實現(xiàn),它負責(zé)將字節(jié)碼分析成運行時數(shù)據(jù)結(jié)構(gòu),并校驗有效性等。該方法是final的,不可重寫。
除此之外,ClassLoader里還包含如下一些普通方法。
- findSystemClass(String name):從本地文件系統(tǒng)裝入文件。它在本地文件系統(tǒng)中尋找類文件,如果存在,就使用defineClass()方法將原始字節(jié)轉(zhuǎn)換成Class對象,以將該文件轉(zhuǎn)換成類;
- static getSystemClassLoader():返回系統(tǒng)類加載器;
- getParent():獲取該類加載器的父類加載器;
- resolveClass(Class<?> c):鏈接指定的類。類加載器可以使用此方法來鏈接類c;
- findLoadedClass(String name):如果此Java虛擬機已經(jīng)加載了名為name的類,則直接返回該類對應(yīng)的Class實例,否則返回null。該方法是Java類加載緩存機制的體現(xiàn)。
四、URLClassLoader類
Java為ClassLoader提供了一個URLClassLoader實現(xiàn)類,該類也是系統(tǒng)類加載器和擴展類加載器的父類(此處的父類,就是指類與類之間的繼承關(guān)系)。URLClassLoader功能比較強大,它既可以從本地文件系統(tǒng)獲取二進制文件來加載類,也可以從遠程主機獲取二進制文件來加載類。
在應(yīng)用程序中可以直接使用URLClassLoader加載類,URLClassLoader類提供了如下兩個構(gòu)造器。
- URLClassLoader(URL[] urls):使用默認的父類加載器創(chuàng)建一個ClassLoader對象,該對象將從urls所指定的系列路徑來查詢并加載類。
- URLClassLoader(URL[] urls, ClassLoader parent):使用指定的父類加載器創(chuàng)建一個ClassLoader對象,其他功能與前一個構(gòu)造器相同。
一旦得到了URLClassLoader對象之后,就可以調(diào)用該對象的loadClass()方法來加載指定類。
到此這篇關(guān)于深入理解Java中的類加載器原理的文章就介紹到這了,更多相關(guān)Java類加載器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Project?Reactor源碼解析publishOn使用示例
這篇文章主要為大家介紹了Project?Reactor源碼解析publishOn使用示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-08-08Java實現(xiàn)多個單張tif文件合并成一個多頁tif文件
業(yè)務(wù)部門需要將多個單張的tiff文件,合并成一個多頁的tiff文件,本文就來介紹一下如何實現(xiàn),具有一定的參考價值,感興趣的可以了解一下2023-09-09Java調(diào)用計算機攝像頭拍照實現(xiàn)過程解析
這篇文章主要介紹了Java調(diào)用計算機攝像頭拍照實現(xiàn)過程解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-05-05java編程經(jīng)典案例之基于斐波那契數(shù)列解決兔子問題實例
這篇文章主要介紹了java編程經(jīng)典案例之基于斐波那契數(shù)列解決兔子問題,結(jié)合完整實例形式分析了斐波那契數(shù)列的原理及java解決兔子問題的相關(guān)操作技巧,需要的朋友可以參考下2017-10-10在Java中關(guān)閉SQL執(zhí)行日志來優(yōu)化服務(wù)器性能
Java應(yīng)用程序中,數(shù)據(jù)庫操作是一個常見的任務(wù),如果不適當?shù)靥幚鞸QL執(zhí)行日志,可能會導(dǎo)致不必要的性能損失,SQL執(zhí)行日志通常由數(shù)據(jù)庫連接池、ORM框架(如Hibernate、MyBatis)、或者應(yīng)用服務(wù)器的內(nèi)置日志機制生成,本文將探討如何在Java中關(guān)閉SQL執(zhí)行日志,提升應(yīng)用性能和效率2024-11-11Java中的HashMap和Hashtable區(qū)別解析
這篇文章主要介紹了Java中的HashMap和Hashtable區(qū)別解析,HashMap和Hashtable都實現(xiàn)了Map接口,但決定用哪一個之前先要弄清楚它們之間的區(qū)別,主要的區(qū)別有線程安全性、同步和速度,需要的朋友可以參考下2023-11-11