《程序員修煉之道》-讀書(shū)筆記八-類(lèi)加載和類(lèi)對(duì)象
一. 類(lèi)的加載和連接預(yù)覽
JVM的目的是使用類(lèi)文件并執(zhí)行其中的字節(jié)碼。要實(shí)現(xiàn)這個(gè)目的,JVM必須以字節(jié)數(shù)據(jù)流的方式取出類(lèi)文件中的內(nèi)容,并將其轉(zhuǎn)換成可用的格式加人運(yùn)行態(tài)中。這個(gè)分兩步走的過(guò)程被稱為加載和連接,但連接又會(huì)被分解為幾個(gè)子階段。
加載
這個(gè)過(guò)程首先要讀取構(gòu)成類(lèi)文件的字節(jié)數(shù)據(jù)流并給類(lèi)的表現(xiàn)形式解凍。該過(guò)程一開(kāi)始是創(chuàng)建一個(gè)字節(jié)數(shù)組,其內(nèi)容通常是從文件系統(tǒng)中讀取的,然后產(chǎn)生與所加載的類(lèi)對(duì)應(yīng)的Class對(duì)象。
在這個(gè)過(guò)程中會(huì)對(duì)類(lèi)做一些基本檢查,但在加載過(guò)程結(jié)束時(shí),Class對(duì)象還不成熟,所以類(lèi)也不可用。
連接
加載完成之后,類(lèi)必須被連接起來(lái)。這一步驟分為三個(gè)子階段一一一驗(yàn)證,準(zhǔn)備和解析。驗(yàn)證階段證實(shí)類(lèi)文件符合預(yù)期,不會(huì)引起系統(tǒng)的運(yùn)行時(shí)錯(cuò)誤或其他問(wèn)題。之后是類(lèi)的準(zhǔn)備階段,在類(lèi)文件中引用的其他類(lèi)型全部都要定位到,以確保該類(lèi)已準(zhǔn)備就緒,連接步驟中各子階段之間的相互關(guān)系如圖所示:

驗(yàn)證
驗(yàn)證是一個(gè)非常復(fù)雜的過(guò)程,它分為幾個(gè)步驟。
首先是完整性檢查。這實(shí)際上是加載過(guò)程中的一部分,會(huì)確保類(lèi)文件格式良好,可以連接。
接著是檢查常量池中的符號(hào)信息是自相一致的,并要遵守常量的基本行為準(zhǔn)則。其他不涉及代碼的靜態(tài)檢查也要在這一階段完成,比如檢查final方法有沒(méi)有被重寫(xiě)。
之后是驗(yàn)證中最復(fù)雜的部分一一方法的字節(jié)碼檢查。要檢查字節(jié)碼行為良好,并且不會(huì)試圖擺脫VM的環(huán)境控制。下面是一些主要檢查。
- 是否所有方法都遵守訪問(wèn)控制關(guān)鍵字的限定。
- 方法調(diào)用的參數(shù)個(gè)數(shù)和靜態(tài)類(lèi)型是否正確
- 確保字節(jié)碼不會(huì)試圖濫用堆棧。
- 確保變量使用之前被正確初始化了。
- 檢查變量是否僅被賦予恰當(dāng)類(lèi)型的值。
做這些檢查是出于性能方面的考慮,這樣可以加快解釋碼的運(yùn)行速度,運(yùn)行時(shí)就不用再做這些檢查了。同時(shí)還簡(jiǎn)化了運(yùn)行時(shí)把字節(jié)碼編譯為機(jī)器碼的過(guò)程(即時(shí)編譯)。
準(zhǔn)備
類(lèi)的準(zhǔn)備包括分配內(nèi)存和準(zhǔn)備好初始化類(lèi)中的靜態(tài)變量,但不會(huì)現(xiàn)在初始化變量,也不會(huì)執(zhí)行任何VM字節(jié)碼。
解析
解析會(huì)促使VM檢查類(lèi)文件中所引用的類(lèi)型是不是都是已知的類(lèi)型。如果有運(yùn)行時(shí)未知的類(lèi)型,那它們也需要被加載進(jìn)來(lái)。這些可見(jiàn)的未知類(lèi)型會(huì)再次引發(fā)類(lèi)加載過(guò)程。
一旦需要加載的其他類(lèi)型全被定位并解析完成,VM就可以初始化那個(gè)最初要加載的類(lèi)。這時(shí)所有靜態(tài)變量都可以被初始化,所有靜態(tài)初始化代碼塊都會(huì)運(yùn)行。現(xiàn)在你運(yùn)行的字節(jié)碼就是來(lái)自新加載進(jìn)來(lái)的類(lèi)里的。這一步完成之后,類(lèi)的加載就已全部完成,類(lèi)也就可以使用了。
二. 類(lèi)加載器
Java平臺(tái)里有幾個(gè)經(jīng)典的類(lèi)加載器,它們?cè)谄脚_(tái)的啟動(dòng)和常規(guī)操作過(guò)程中承擔(dān)不同的任務(wù):
- 根(或引導(dǎo))類(lèi)加載器一一通常在VM啟動(dòng)后不久實(shí)例化,一般用本地代碼實(shí)現(xiàn)。最好把它看做VM的一部分。它的作用通常是負(fù)責(zé)加載系統(tǒng)的基礎(chǔ)JAR(主要是rt.jar),而且它不做驗(yàn)證工作。
- 擴(kuò)展類(lèi)加載器一一用來(lái)加載安裝時(shí)自帶的標(biāo)準(zhǔn)擴(kuò)展。一般包括安全性擴(kuò)展。
- 應(yīng)用(或系統(tǒng))類(lèi)加載器一一這是應(yīng)用最廣泛的類(lèi)加載器。它負(fù)責(zé)加載應(yīng)用類(lèi)。在大多數(shù)SE(Java標(biāo)準(zhǔn)版)的環(huán)境中,主要工作都是由它來(lái)完成。
- 定制類(lèi)加載器一一在更復(fù)雜的環(huán)境中,比如EE(Java企業(yè)版)或比較復(fù)雜的SE框架,通常會(huì)有些附加(即定制)的類(lèi)加載器。有些團(tuán)隊(duì)甚至為他們的某個(gè)應(yīng)用程序編寫(xiě)了特定的類(lèi)加載器。
除了核心任務(wù),類(lèi)加載器還經(jīng)常要從JAR文件或classpath中加載資源(不是類(lèi)文件,比如圖片或配置文件)。
有些框架和代碼還經(jīng)常會(huì)使用帶有額外屬性的專用(甚至用戶自定義的)類(lèi)加載器。這些類(lèi)加載器經(jīng)常會(huì)在加載時(shí)對(duì)字節(jié)碼進(jìn)行轉(zhuǎn)換。
下圖是類(lèi)加載器的繼承層級(jí)以及不同加載器之間的相互關(guān)系。

這其中涉及到了雙親委派機(jī)制,雙親委派模式要求除了頂層的啟動(dòng)類(lèi)加載器外,其余的類(lèi)加載器都應(yīng)當(dāng)有自己的父類(lèi)加載器,請(qǐng)注意雙親委派模式中的父子關(guān)系并非通常所說(shuō)的類(lèi)繼承關(guān)系,而是采用組合關(guān)系來(lái)復(fù)用父類(lèi)加載器的相關(guān)代碼
當(dāng)一個(gè)類(lèi)加載器收到了類(lèi)加載請(qǐng)求時(shí),會(huì)先把這個(gè)請(qǐng)求委托給父類(lèi)加載器去執(zhí)行,如果這個(gè)父類(lèi)加載器上邊還有加載器,則繼續(xù)向上委托,直到到達(dá)頂層的啟動(dòng)類(lèi)加載器.如果父類(lèi)可以執(zhí)行成功,則直接返回結(jié)果,如果父類(lèi)無(wú)法加載,則再讓子類(lèi)去嘗試加載.

浙公網(wǎng)安備 33010602011771號(hào)