干Android開發(fā)兩年了,竟然不清楚Java類加載過程玄柠,都有點(diǎn)不好意思說自己會Java,這不趕快來惡補(bǔ)一下這方面的知識诫舅,搞明白類加載過程后羽利,會對其他技術(shù)知識譬如:static關(guān)鍵字的作用?反射機(jī)制刊懈?等等都有幫助这弧,這也是為什么知識一定要系統(tǒng)化的原因。
首先我們要了解一下ClassLoader虚汛,ClassLoader是Java用于加載類的一個機(jī)制匾浪。等到程序運(yùn)行時,JVM先初始化卷哩,在JVM初始化的過程中蛋辈,JVM生成幾個ClassLoader,JVM調(diào)用指定的ClassLoader去加載.class文件等各類路徑将谊、文件的類冷溶。
程序運(yùn)行時類的加載實(shí)際過程
1请垛、JDK執(zhí)行指令去尋找jre目錄叨粘,尋找jvm.dll赘理,并初始化JVM居兆;產(chǎn)生一個Bootstrap Loader(啟動類加載器);
2甘晤、Bootstrap Loader自動加載Extended Loader(標(biāo)準(zhǔn)擴(kuò)展類加載器)乳绕,并將其父Loader設(shè)為Bootstrap Loader享潜。
3瓦堵、Bootstrap Loader自動加載AppClass Loader(系統(tǒng)類加載器)柒巫,并將其父Loader設(shè)為Extended Loader。
4谷丸、最后由AppClass Loader加載Java類。
Java類加載過程即類裝載器把一個類裝入Java虛擬機(jī)中应结,總體來說包含以下過程:
編譯 -> 加載 -> 鏈接(驗(yàn)證+準(zhǔn)備+解析)->初始化(使用前的準(zhǔn)備)->使用-> 卸載
1刨疼、加載:以二進(jìn)制形式生成Class對象
⑴、通過一個類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流鹅龄。
⑵揩慕、將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時數(shù)據(jù)結(jié)構(gòu)。
⑶扮休、在java堆中生成一個代表這個類的java.lang.Class對象迎卤,作為方法區(qū)這些數(shù)據(jù)的訪問入口。
第一步獲取二進(jìn)制字節(jié)流有很多形式玷坠,因?yàn)樗]有限定二進(jìn)制流從哪里來蜗搔,那么我們可以 用系統(tǒng)的類加載器劲藐,也可以用自己的方式寫加載器來控制字節(jié)流的獲取 :
①從class文件來->一般的文件加載
②從zip包中來->加載jar中的類
③從網(wǎng)絡(luò)中來->Applet
獲取二進(jìn)制流獲取完成后會按照jvm所需的方式保存在方法區(qū)中,同時會在java堆中實(shí)例化一個java.lang.Class對象與堆中的數(shù)據(jù)關(guān)聯(lián)起來樟凄。
2聘芜、鏈接:又分驗(yàn)證、準(zhǔn)備缝龄、解析
⑴汰现、驗(yàn)證:檢查導(dǎo)入類或接口二進(jìn)制數(shù)據(jù)的正確
⑵ 、準(zhǔn)備:為靜態(tài)變量初始化并分配內(nèi)存地址
⑶叔壤、 解析:將符號引用轉(zhuǎn)為直接引用
第一步:驗(yàn)證
驗(yàn)證又可以細(xì)分一下幾個步驟: 文件格式驗(yàn)證->元數(shù)據(jù)驗(yàn)證->字節(jié)碼驗(yàn)證->符號引用驗(yàn)證
① 文件格式驗(yàn)證:驗(yàn)證字節(jié)流是否符合Class文件格式的規(guī)范并 驗(yàn)證其版本是否能被當(dāng)前的jvm版本所處理瞎饲。ok沒問題后,字節(jié)流就可以進(jìn)入內(nèi)存的方法區(qū)進(jìn)行保存了炼绘。后面的3個校驗(yàn)都是在方法區(qū)進(jìn)行的嗅战。
② 元數(shù)據(jù)驗(yàn)證 :對字節(jié)碼描述的信息進(jìn)行語義化分析,保證其描述的內(nèi)容符合java語言的語法規(guī)范饭望。
③字節(jié)碼檢驗(yàn) :最復(fù)雜仗哨,對方法體的內(nèi)容進(jìn)行檢驗(yàn),保證其在運(yùn)行時不會作出什么出格的事來铅辞。
④符號引用驗(yàn)證 :來驗(yàn)證一些引用的真實(shí)性與可行性厌漂,比如代碼里面引了其他類,這里就要去檢測一下那些來究竟是否存在斟珊;或者說代碼中訪問了其他類的一些屬性苇倡,這里就對那些屬性的可以訪問行進(jìn)行了檢驗(yàn)。(這一步將為后面的解析工作打下基礎(chǔ))
驗(yàn)證的目的:確保class文件的字節(jié)流信息符合jvm的規(guī)范囤踩。假如jvm不對這些數(shù)據(jù)進(jìn)行校驗(yàn)的話旨椒,可能一些有害的字節(jié)流會讓jvm完全崩潰。
驗(yàn)證階段很重要堵漱,但也不是必要的综慎,假如說一些代碼被反復(fù)使用并驗(yàn)證過可靠性了,實(shí)施階段就可以嘗試用-Xverify:none參數(shù)來關(guān)閉大部分的類驗(yàn)證措施勤庐,以簡短類加載時間示惊。
第二步:準(zhǔn)備
這階段會為類變量(指那些靜態(tài)變量)分配內(nèi)存并設(shè)置類比那輛初始值的階段,這些內(nèi)存在方法區(qū)中進(jìn)行分配愉镰。這里要說明一下米罚,這一步只會給那些靜態(tài)變量設(shè)置一個初始的值,而那些實(shí)例變量是在實(shí)例化對象時進(jìn)行分配的丈探。
例如:
public static int value=123; 此時value的值為0录择,不是123。
private int i = 123; 此時,i 還未進(jìn)行初始化隘竭,因?yàn)檫@句代碼還不能執(zhí)行塘秦。
第三步:解析
是對類的屬性,方法等東西進(jìn)行轉(zhuǎn)換货裹,具體涉及到Class文件的格式內(nèi)容嗤形。
3、初始化:激活類的靜態(tài)變量和靜態(tài)代碼塊弧圆,初始化Java代碼
要對類進(jìn)行 初始化 赋兵,代碼上可以理解為 ‘為要初始化的類中的所有靜態(tài)成員都賦予初始值、對類中所有靜態(tài)塊都執(zhí)行一次搔预,并且是按代碼編寫順序執(zhí)行’ 霹期。
如下代碼:輸出的是‘1’。如果①和②順序調(diào)換拯田,則輸出的是‘123’历造。
public class Main {
public static void main(String[] args){
System.out.println(Super.i);
}
}
class Super{
//①
static{
i = 123;
}
//②
protected static int i = 1;
}
主動對類進(jìn)行引用”指的就是以下五種JVM規(guī)定的判定初始化與否的預(yù)處理?xiàng)l件。那么船庇,其他的方式吭产,都可歸為‘類被動引用’的方式,這些方式是不會引起JVM去初始化相關(guān)類的:
1.用new實(shí)例化一個類時
讀取或者設(shè)置類的靜態(tài)字段時(不包括被final修飾的靜態(tài)字段鸭轮,因?yàn)樗麄円呀?jīng)被塞進(jìn)常量池了)
2.執(zhí)行靜態(tài)方法的時候臣淤。
使用java.lang.reflect.*的方法對類進(jìn)行反射調(diào)用的時候,如果類還沒有進(jìn)行過初始化窃爷,馬上對其進(jìn)行邑蒋。
3.初始化一個類的時候,如果他的父親還沒有被初始化按厘,則先去初始化其父親医吊。
4.當(dāng)jvm啟動時
用戶需要指定一個要執(zhí)行的主類(包含static void main(String[] args)的那個類),則jvm會先去初始化這個類逮京。
5.用Class.forName(String className);來加載類的時候卿堂,也會執(zhí)行初始化動作。
注意:ClassLoader的loadClass(String className);方法只會加載并編譯某類懒棉,并不會對其執(zhí)行初始化
因此類從編譯御吞、被使用,到卸載的全過程:
編譯 -> 加載 -> 鏈接(驗(yàn)證+準(zhǔn)備+解析)->初始化(使用前的準(zhǔn)備)->使用-> 卸載