類從被加載到JVM中開(kāi)始两蟀,到卸載為止网梢,整個(gè)生命周期包括:加載、驗(yàn)證赂毯、準(zhǔn)備战虏、解析、初始化党涕、使用和卸載七個(gè)階段烦感。
其中類加載過(guò)程包括加載、驗(yàn)證膛堤、準(zhǔn)備手趣、解析和初始化五個(gè)階段。
(1)類加載器的任務(wù)就是根據(jù)一個(gè)類的全限定名來(lái)讀取此類的二進(jìn)制字節(jié)流到JVM中肥荔,然后轉(zhuǎn)換為一個(gè)與目標(biāo)類對(duì)應(yīng)的java.lang.Class對(duì)象實(shí)例绿渣。BootstrapClassLoader、ExtClassLoader和AppClassLoaderdefineClass方法將字節(jié)碼的byte數(shù)組轉(zhuǎn)換為一個(gè)類的class對(duì)象實(shí)例燕耿,如果希望在類被記載到JVM時(shí)就被鏈接中符,那么可以調(diào)用resolveClass方法。
(2)自定義類加載器需要繼承抽象類ClassLoader誉帅,實(shí)現(xiàn)findClass方法淀散,該方法會(huì)在loadClass調(diào)用的時(shí)候被調(diào)用,findClass默認(rèn)會(huì)拋出異常蚜锨。findClass方法表示根據(jù)類名查找類對(duì)象档插,loadClass方法表示根據(jù)類名進(jìn)行雙親委托模型進(jìn)行類加載并返回類對(duì)象,defineClass方法表示跟根據(jù)類的字節(jié)碼轉(zhuǎn)換為類對(duì)象
雙親委托模型亚再,約定類加載器的加載機(jī)制:
(1)當(dāng)一個(gè)類加載器接收到一個(gè)類加載的任務(wù)時(shí)郭膛,不會(huì)立即展開(kāi)加載,而是將加載任務(wù)委托給它的父類加載器去執(zhí)行氛悬,每一層的類都采用相同的方式则剃,直至委托給最頂層的啟動(dòng)類加載器為止凄诞。如果父類加載器無(wú)法加載委托給它的類,便將類的加載任務(wù)退回給下一級(jí)類加載器去執(zhí)行加載忍级。
(2)使用雙親委托機(jī)制的好處是:能夠有效確保一個(gè)類的全局唯一性帆谍,當(dāng)程序中出現(xiàn)多個(gè)限定名相同的類時(shí),類加載器在執(zhí)行加載時(shí)轴咱,始終只會(huì)加載其中的某一個(gè)類汛蝙。
(3)使用雙親委托模型來(lái)組織類加載器之間的關(guān)系,有一個(gè)顯而易見(jiàn)的好處就是Java類隨著它的類加載器一起具備了一種帶有優(yōu)先級(jí)的層次關(guān)系朴肺。例如類java.lang.Object窖剑,它存放在rt.jar之中,無(wú)論哪一個(gè)類加載器要加載這個(gè)類戈稿,最終都是委托給處于模型最頂端的啟動(dòng)類加載器進(jìn)行加載西土,因此Object類在程序的各種加載器環(huán)境中都是同一個(gè)類。相反鞍盗,如果沒(méi)有使用雙親委托模型需了,由各個(gè)類加載器自行去加載的話,如果用戶自己編寫(xiě)了一個(gè)稱為java.lang.Object的類般甲,并放在程序的ClassPath中肋乍,那系統(tǒng)中將會(huì)出現(xiàn)多個(gè)不同的Object類,Java類型體系中最基礎(chǔ)的行為也就無(wú)法保證敷存,應(yīng)用程序也將會(huì)變得一片混亂墓造。如果自己去編寫(xiě)一個(gè)與rt.jar類庫(kù)中已有類重名的Java類,將會(huì)發(fā)現(xiàn)可以正常編譯锚烦,但永遠(yuǎn)無(wú)法被加載運(yùn)行觅闽。
(4)雙親委托模型對(duì)于保證Java程序的穩(wěn)定運(yùn)作很重要,但它的實(shí)現(xiàn)卻非常簡(jiǎn)單涮俄,實(shí)現(xiàn)雙親委托的代碼都集中在java.lang.ClassLoader的loadClass()方法中蛉拙,邏輯清晰易懂:先檢查是否已經(jīng)被加載過(guò),若沒(méi)有加載則調(diào)用父類加載器的loadClass()方法禽拔,若父加載器為空則默認(rèn)使用啟動(dòng)類加載器作為父加載器刘离。如果父類加載器加載失敗室叉,拋出ClassNotFoundException異常后睹栖,再調(diào)用自己的findClass方法進(jìn)行加載。
1茧痕、加載
簡(jiǎn)單的說(shuō)野来,類加載階段就是由類加載器負(fù)責(zé)根據(jù)一個(gè)類的全限定名來(lái)讀取此類的二進(jìn)制字節(jié)流到JVM內(nèi)部,并存儲(chǔ)在運(yùn)行時(shí)內(nèi)存區(qū)的方法區(qū)踪旷,然后將其轉(zhuǎn)換為一個(gè)與目標(biāo)類型對(duì)應(yīng)的java.lang.Class對(duì)象實(shí)例(Java虛擬機(jī)規(guī)范并沒(méi)有明確要求一定要存儲(chǔ)在堆區(qū)中曼氛,只是hotspot選擇將Class對(duì)戲那個(gè)存儲(chǔ)在方法區(qū)中)豁辉,這個(gè)Class對(duì)象在日后就會(huì)作為方法區(qū)中該類的各種數(shù)據(jù)的訪問(wèn)入口。
2舀患、鏈接
鏈接階段要做的是將加載到JVM中的二進(jìn)制字節(jié)流的類數(shù)據(jù)信息合并到JVM的運(yùn)行時(shí)狀態(tài)中徽级,經(jīng)由驗(yàn)證、準(zhǔn)備和解析三個(gè)階段聊浅。
1)餐抢、驗(yàn)證
驗(yàn)證類數(shù)據(jù)信息是否符合JVM規(guī)范,是否是一個(gè)有效的字節(jié)碼文件低匙,驗(yàn)證內(nèi)容涵蓋了類數(shù)據(jù)信息的格式驗(yàn)證旷痕、語(yǔ)義分析、操作驗(yàn)證等顽冶。
格式驗(yàn)證:驗(yàn)證是否符合class文件規(guī)范
語(yǔ)義驗(yàn)證:檢查一個(gè)被標(biāo)記為final的類型是否包含子類欺抗;檢查一個(gè)類中的final方法視頻被子類進(jìn)行重寫(xiě);確保父類和子類之間沒(méi)有不兼容的一些方法聲明(比如方法簽名相同强重,但方法的返回值不同)
操作驗(yàn)證:在操作數(shù)棧中的數(shù)據(jù)必須進(jìn)行正確的操作绞呈,對(duì)常量池中的各種符號(hào)引用執(zhí)行驗(yàn)證(通常在解析階段執(zhí)行,檢查是否通過(guò)富豪引用中描述的全限定名定位到指定類型上间景,以及類成員信息的訪問(wèn)修飾符是否允許訪問(wèn)等)
3)报强、解析將常量池中的符號(hào)引用轉(zhuǎn)為直接引用(得到類或者字段、方法在內(nèi)存中的指針或者偏移量拱燃,以便直接調(diào)用該方法)秉溉,這個(gè)可以在初始化之后再執(zhí)行。
可以認(rèn)為是一些靜態(tài)綁定的會(huì)被解析碗誉,動(dòng)態(tài)綁定則只會(huì)在運(yùn)行是進(jìn)行解析召嘶;靜態(tài)綁定包括一些final方法(不可以重寫(xiě)),static方法(只會(huì)屬于當(dāng)前類),構(gòu)造器(不會(huì)被重寫(xiě))
3哮缺、初始化
將一個(gè)類中所有被static關(guān)鍵字標(biāo)識(shí)的代碼統(tǒng)一執(zhí)行一遍弄跌,如果執(zhí)行的是靜態(tài)變量,那么就會(huì)使用用戶指定的值覆蓋之前在準(zhǔn)備階段設(shè)置的初始值尝苇;如果執(zhí)行的是static代碼塊铛只,那么在初始化階段,JVM就會(huì)執(zhí)行static代碼塊中定義的所有操作糠溜。
所有類變量初始化語(yǔ)句和靜態(tài)代碼塊都會(huì)在編譯時(shí)被前端編譯器放在收集器里頭淳玩,存放到一個(gè)特殊的方法中,這個(gè)方法就是方法非竿,即類/接口初始化方法蜕着。該方法的作用就是初始化一個(gè)中的變量,使用用戶指定的值覆蓋之前在準(zhǔn)備階段里設(shè)定的初始值红柱。任何invoke之類的字節(jié)碼都無(wú)法調(diào)用方法承匣,因?yàn)樵摲椒ㄖ荒茉陬惣虞d的過(guò)程中由JVM調(diào)用蓖乘。
如果父類還沒(méi)有被初始化,那么優(yōu)先對(duì)父類初始化韧骗,但在方法內(nèi)部不會(huì)顯示調(diào)用父類的方法嘉抒,由JVM負(fù)責(zé)保證一個(gè)類的方法執(zhí)行之前,它的父類方法已經(jīng)被執(zhí)行袍暴。
JVM必須確保一個(gè)類在初始化的過(guò)程中众眨,如果是多線程需要同時(shí)初始化它,僅僅只能允許其中一個(gè)線程對(duì)其執(zhí)行初始化操作容诬,其余線程必須等待娩梨,只有在活動(dòng)線程執(zhí)行完對(duì)類的初始化操作之后,才會(huì)通知正在等待的其他線程览徒。