java類加載機(jī)制、類加載器、自定義類加載器的案例
java類從被加載到JVM到卸載出JVM,整個(gè)生命周期包括:加載(Loading)、驗(yàn)證(Verification)、準(zhǔn)備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(using)、和卸載(Unloading)七個(gè)階段。
其中驗(yàn)證、準(zhǔn)備和解析三個(gè)部分統(tǒng)稱為連接(Linking)。
加載指的是將類的.class文件中的二進(jìn)制數(shù)據(jù)讀入到內(nèi)存中,將其放在運(yùn)行時(shí)數(shù)據(jù)區(qū)的方法區(qū)內(nèi),然后在堆區(qū)創(chuàng)建一個(gè)java.lang.Class對(duì)象,用來(lái)封裝類在方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)。
類的加載通過(guò)JVM提供的類加載器完成,類加載器是程序運(yùn)行的基礎(chǔ)。程序在啟動(dòng)的時(shí)候,并不會(huì)一次性加載程序所要用到的所有class文件,而是根據(jù)需要,通過(guò)java的類加載器機(jī)制(classLoader)來(lái)動(dòng)態(tài)加載某個(gè)class文件到內(nèi)存中。
jvm在運(yùn)行時(shí)會(huì)產(chǎn)生三個(gè)classLoader:
啟動(dòng)類加載器(BootStrap ClassLoader):是java類加載層次中最頂層的類加載器,負(fù)責(zé)加載jdk中的核心類庫(kù)。由C++實(shí)現(xiàn),不是classLoader的子類。
擴(kuò)展類加載器(Extension ClassLoader):負(fù)責(zé)加載java的擴(kuò)展類庫(kù),比如lib/ext或者java.ext.dirs系統(tǒng)屬性指定的目錄中的jar包。父類加載器為null。
系統(tǒng)類加載器(App ClassLoader):負(fù)責(zé)加載來(lái)自java命令的-classpath選項(xiàng)、java.class.path系統(tǒng)屬性所指定的jar包和類路徑。程序可以通過(guò)classLoader的靜態(tài)方法getSystemClassLoader(),來(lái)獲取系統(tǒng)類加載器。由java語(yǔ)言實(shí)現(xiàn),父類加載器為ExtClassLoader。
除了java默認(rèn)提供的這三個(gè)classLoader之外,用戶可以根據(jù)需要定義自己的classLoader,這些自定義的classLoader都必須繼承自java.lang.ClassLoader類。
通過(guò)使用不同的類加載器,可以從不同來(lái)源加載類的二進(jìn)制數(shù)據(jù)。通常有如下幾種情況:
從本地文件系統(tǒng)加載class文件,這是絕大部分實(shí)例程序的類加載方式。從jar包加載class類,這種方式也很常見(jiàn)。通過(guò)網(wǎng)絡(luò)加載class類把一個(gè)java源文件動(dòng)態(tài)編譯,并執(zhí)行加載,比如jsp。
2、連接當(dāng)類被加載之后,系統(tǒng)為之生成一個(gè)對(duì)應(yīng)的class對(duì)象,接著進(jìn)入連接階段(驗(yàn)證-準(zhǔn)備-解析),連接階段負(fù)責(zé)把類的二進(jìn)制數(shù)據(jù)合并到j(luò)re中。
驗(yàn)證:用于檢測(cè)被加載的類是否有正確的內(nèi)部結(jié)構(gòu),并和其他類協(xié)調(diào)一致。
包括四種驗(yàn)證:文件格式驗(yàn)證、元數(shù)據(jù)驗(yàn)證、字節(jié)驗(yàn)證和符號(hào)引用驗(yàn)證。準(zhǔn)備:負(fù)責(zé)為類變量分配內(nèi)存,并設(shè)置默認(rèn)初始值。
解析:將類的二進(jìn)制數(shù)據(jù)中的變量進(jìn)行符號(hào)引用替換成直接引用。
3、初始化在初始化階段,主要為類的靜態(tài)變量賦予正確的初始值。其實(shí)就是執(zhí)行類構(gòu)造器<clinit>()方法的過(guò)程。
在java類中對(duì)類變量指定初始值有兩種方式:a.聲明類變量時(shí)指定初始值;b.使用靜態(tài)初始化塊為類變量指定初始值。
jvm初始化一個(gè)類包含如下步驟:
加載并連接該類先初始化其直接父類依次執(zhí)行初始化語(yǔ)句當(dāng)執(zhí)行第2步時(shí),系統(tǒng)對(duì)直接父類的初始化也遵循1~3,以此類推。
當(dāng)一個(gè)類被主動(dòng)引用后會(huì)觸發(fā)初始化過(guò)程:
遇到new、getstatic、putstatic或invokestatic這4條字節(jié)碼指令時(shí),如果類沒(méi)有進(jìn)行過(guò)初始化,則需要先觸發(fā)其初始化。
生成這4條指令最常見(jiàn)的Java代碼場(chǎng)景是:使用new關(guān)鍵字實(shí)例化對(duì)象時(shí)、讀取或者設(shè)置一個(gè)類的靜態(tài)字段(被final修飾、已在編譯器把結(jié)果放入常量池的靜態(tài)字段除外)時(shí)、以及調(diào)用一個(gè)類的靜態(tài)方法的時(shí)候。
使用java.lang.reflect包的方法對(duì)類進(jìn)行反射調(diào)用的時(shí)候,如果類沒(méi)有進(jìn)行過(guò)初始化,則需要先觸發(fā)其初始化。
當(dāng)初始化一個(gè)類的時(shí)候,如果發(fā)現(xiàn)其父類還沒(méi)有進(jìn)行過(guò)初始化,則需要觸發(fā)父類的初始化。當(dāng)虛擬機(jī)啟動(dòng)時(shí),用戶需要指定一個(gè)執(zhí)行的主類(包含main()方法的類),虛擬機(jī)會(huì)先初始化這個(gè)類。
當(dāng)使用jdk7+的動(dòng)態(tài)語(yǔ)言支持時(shí),如果java.lang.invoke.MethodHandle實(shí)例最后的解析結(jié)果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且這個(gè)方法句柄所對(duì)應(yīng)的類沒(méi)有進(jìn)行過(guò)初始化,則需要先觸發(fā)器 初始化。
當(dāng)一個(gè)類如果是被動(dòng)引用的話,不會(huì)觸發(fā)初始化過(guò)程:
通過(guò)子類引用父類的靜態(tài)字段,不會(huì)導(dǎo)致子類初始化。對(duì)于靜態(tài)字段,只有直接定義該字段的類才會(huì)被初始化,因此當(dāng)我們通過(guò)子類來(lái)引用父類中定義的靜態(tài)字段時(shí),只會(huì)觸發(fā)父類的初始化,而不會(huì)觸發(fā)子類的初始化。
通過(guò)數(shù)組定義來(lái)引用類,不會(huì)觸發(fā)此類的初始化。
常量在編譯階段會(huì)存入調(diào)用類的常量池中,本質(zhì)上沒(méi)有直接引用到定義常量的類,因此不會(huì)觸發(fā)定義常量的類的初始化。
4、使用(略)
5、卸載如果出現(xiàn)下面的情況,類就會(huì)被卸載:
該類所有的實(shí)例都已經(jīng)被回收,也就是java堆中不存在該類的任何實(shí)例。加載該類的ClassLoader已經(jīng)被回收。
該類對(duì)應(yīng)的java.lang.Class對(duì)象沒(méi)有任何地方被引用,無(wú)法在任何地方通過(guò)反射訪問(wèn)該類的方法。
如果以上三個(gè)條件全部滿足,jvm就會(huì)在方法區(qū)垃圾回收的時(shí)候?qū)︻愡M(jìn)行卸載,類的卸載過(guò)程其實(shí)就是在方法區(qū)中清空類信息,java類的整個(gè)生命周期就結(jié)束了。
類加載器類加載器負(fù)責(zé)加載所有的類。其為所有被載入內(nèi)存中的類生成一個(gè)java.lang.Class實(shí)例對(duì)象。一旦一個(gè)類被加載如JVM中,同一個(gè)類就不會(huì)被再次載入了。
正如一個(gè)對(duì)象有一個(gè)唯一的標(biāo)識(shí)一樣,一個(gè)載入JVM的類也有一個(gè)唯一的標(biāo)識(shí)。
在Java中,一個(gè)類用其全限定類名(包括包名和類名)作為標(biāo)識(shí);但在JVM中,一個(gè)類用其全限定類名和其類加載器作為其唯一標(biāo)識(shí)。
例如,如果在pg的包中有一個(gè)名為Person的類,被類加載器ClassLoader的實(shí)例kl負(fù)責(zé)加載,則該P(yáng)erson類對(duì)應(yīng)的Class對(duì)象在JVM中表示為(Person.pg.kl)。
這意味著兩個(gè)類加載器加載的同名類:(Person.pg.kl)和(Person.pg.kl2)是不同的、它們所加載的類也是完全不同、互不兼容的。
前面我們已經(jīng)介紹了java中的幾種類加載器,下面我們用一張圖展示他們的層次關(guān)系:
類加載器加載class大致需要如下8個(gè)步驟:
檢測(cè)此Class是否載入過(guò),即在緩沖區(qū)中是否有此Class,如果有直接進(jìn)入第8步,否則進(jìn)入第2步。
如果沒(méi)有父類加載器,則要么Parent是根類加載器,要么本身就是根類加載器,則跳到第4步,如果父類加載器存在,則進(jìn)入第3步。
請(qǐng)求使用父類加載器去載入目標(biāo)類,如果載入成功則跳至第8步,否則接著執(zhí)行第5步。
請(qǐng)求使用根類加載器去載入目標(biāo)類,如果載入成功則跳至第8步,否則跳至第7步。
當(dāng)前類加載器嘗試尋找Class文件,如果找到則執(zhí)行第6步,如果找不到則執(zhí)行第7步。
從文件中載入Class,成功后跳至第8步。
拋出ClassNotFountException異常。返回對(duì)應(yīng)的java.lang.Class對(duì)象。
類加載機(jī)制全盤負(fù)責(zé):當(dāng)一個(gè)類加載器負(fù)責(zé)加載某個(gè)Class時(shí),該Class所依賴和引用其他Class也將由該類加載器負(fù)責(zé)載入,除非顯示使用另外一個(gè)類加載器來(lái)載入。
雙親委派:先讓父類加載器試圖加載該Class,只有在父類加載器無(wú)法加載該類時(shí)才嘗試從自己的類路徑中加載該類。
通俗的講,就是某個(gè)特定的類加載器在接到加載類的請(qǐng)求時(shí),首先將加載任務(wù)委托給父加載器,依次遞歸,如果父加載器可以完成類加載任務(wù),就成功返回;只有父加載器無(wú)法完成此加載任務(wù)時(shí),才自己去加載。
緩存機(jī)制:保證所有加載過(guò)的Class都會(huì)被緩存,當(dāng)程序中需要使用某個(gè)Class時(shí),類加載器先從緩存區(qū)中搜尋該Class,只有當(dāng)緩存區(qū)中不存在該Class對(duì)象時(shí),系統(tǒng)才會(huì)讀取該類對(duì)應(yīng)的二進(jìn)制數(shù)據(jù),并將其轉(zhuǎn)換成Class對(duì)象,存入緩沖區(qū)中。
這就是為很么修改了Class后,必須重新啟動(dòng)JVM,程序所做的修改才會(huì)生效的原因。
自定義的類加載器jvm除跟類加載器之外的所有類加載器都是ClassLoader子類的實(shí)例,開(kāi)發(fā)者可以通過(guò)拓展ClassLoader的子類,并重寫該ClassLoader所包含的方法實(shí)現(xiàn)自定義的類加載器。
ClassLoader有如下兩個(gè)關(guān)鍵方法:loadClass(String name,boolean resolve):該方法為ClassLoader的入口點(diǎn),根據(jù)指定名稱來(lái)加載類,系統(tǒng)就是調(diào)用ClassLoader的該方法來(lái)獲取指定類的class對(duì)象。
findClass(String name):根據(jù)指定名稱來(lái)查找類如果需要實(shí)現(xiàn)自定義的ClassLoader,則可以通過(guò)重寫以上兩個(gè)方法來(lái)實(shí)現(xiàn),通常推薦重寫findClass()方法而不是loadClass()方法。
classLoader()方法的執(zhí)行步驟:1)findLoadedClass():來(lái)檢查是否加載類,如果加載直接返回;
2)父類加載器上調(diào)用loadClass()方法。如果父類加載器為null,則使用跟類加載器加載;
3)調(diào)用findClass(String)方法查找類。從這邊可以看出,重寫findClass()方法可以避免覆蓋默認(rèn)類加載器的父類委托,緩沖機(jī)制兩種策略;如果重寫loadClass()方法,則實(shí)現(xiàn)邏輯更為復(fù)雜。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持好吧啦網(wǎng)。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。
相關(guān)文章:
1. Intellij IDEA 2019 最新亂碼問(wèn)題及解決必殺技(必看篇)2. 利用django創(chuàng)建一個(gè)簡(jiǎn)易的博客網(wǎng)站的示例3. ASP.NET MVC獲取多級(jí)類別組合下的產(chǎn)品4. PHP5.0正式發(fā)布 不完全兼容PHP4 新增多項(xiàng)功能5. Android自定義View實(shí)現(xiàn)掃描效果6. 未來(lái)的J2EE主流應(yīng)用框架:對(duì)比Spring和EJB37. JS+css3實(shí)現(xiàn)幻燈片輪播圖8. 《javascript設(shè)計(jì)模式》學(xué)習(xí)筆記三:Javascript面向?qū)ο蟪绦蛟O(shè)計(jì)單例模式原理與實(shí)現(xiàn)方法分析9. JS繪圖Flot如何實(shí)現(xiàn)動(dòng)態(tài)可刷新曲線圖10. 關(guān)于HTML5的img標(biāo)簽
