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