類(lèi)的加載階段
類(lèi)的整個(gè)生存周期包括:加載平夜、驗(yàn)證腰鬼、準(zhǔn)備藐握、解析、初始化垃喊。
加載
加載時(shí)類(lèi)的加載階段的開(kāi)始,包括三步
1)ClassLoader以全限定名形式從class文件中讀取二進(jìn)制數(shù)據(jù)流
2)將字節(jié)碼轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)
3)創(chuàng)建class文件對(duì)象袜炕,存放在堆中本谜,作為程序訪問(wèn)方法區(qū)中字節(jié)碼的入口
校驗(yàn)
驗(yàn)證輸入的字節(jié)流是否符合java虛擬機(jī)規(guī)范以及java語(yǔ)義,要保證到達(dá)解析階段時(shí)的正確性偎窘,包括:文件格式校驗(yàn)乌助、元數(shù)據(jù)校驗(yàn)溜在、字節(jié)碼校驗(yàn)、符號(hào)引用校驗(yàn)
文件格式校驗(yàn)
例如有魔術(shù)校驗(yàn)他托、class文件版本是否被當(dāng)前java虛擬機(jī)支持掖肋、常量池中的常量是否是支持的類(lèi)型。文件格式校驗(yàn)主要是保證輸入的字節(jié)流能被正確的解析并存儲(chǔ)到方法區(qū)中赏参,經(jīng)過(guò)了這個(gè)階段的驗(yàn)證志笼,字節(jié)流才會(huì)被存儲(chǔ)到方法區(qū)中。因此把篓,后續(xù)的三個(gè)校驗(yàn)都是對(duì)方法區(qū)中數(shù)據(jù)進(jìn)行校驗(yàn)的纫溃。
元數(shù)據(jù)校驗(yàn)
主要是進(jìn)行語(yǔ)義校驗(yàn),例如
1)是否有父類(lèi)韧掩,父類(lèi)是否是final類(lèi)型
2)如果當(dāng)前類(lèi)不是抽象類(lèi)紊浩,那么是否實(shí)現(xiàn)了父類(lèi)或接口需要實(shí)現(xiàn)的所有方法
3)類(lèi)中的字段、方法是否與父類(lèi)發(fā)生沖突(final類(lèi)型不允許重寫(xiě)疗锐;重寫(xiě)的方法是否符合規(guī)范等)
準(zhǔn)備
準(zhǔn)備階段是為類(lèi)的靜態(tài)成員變量賦予初始值的一個(gè)過(guò)程坊谁,對(duì)應(yīng)內(nèi)存將在方法區(qū)中進(jìn)行分配。
例如 public static int value = 1;
那么準(zhǔn)備階段value會(huì)被賦值為0滑臊,賦值為1的操作是在初始化階段完成的口芍。還有一種情況:
public static final int value = 1;此時(shí)在準(zhǔn)備階段value就會(huì)被賦值為1.
解析
解析就是將常量池中符號(hào)引用替換為直接引用的過(guò)程。解析主要針對(duì):類(lèi)简珠、接口阶界、字段、類(lèi)方法聋庵、接口方法膘融。
此時(shí)類(lèi)方法解析的只是一部分在編譯期確定,運(yùn)行時(shí)已知的方法祭玉,包括構(gòu)造方法氧映、final修飾的方法、私有方法脱货、靜態(tài)方法岛都、類(lèi)方法。但是像重寫(xiě)的方法是無(wú)法做到這一點(diǎn)的振峻,重寫(xiě)的方法需要在運(yùn)行時(shí)做動(dòng)態(tài)解析臼疫,舉個(gè)例子:
輸出結(jié)果很簡(jiǎn)單。分析下main()方法的執(zhí)行過(guò)程:
java虛擬機(jī)中將Human man和Human woman稱(chēng)為靜態(tài)變量扣孟,右側(cè)的對(duì)象稱(chēng)為實(shí)際用例烫堤。如果是方法重載,例如a(Human human),a(Man man),a(Woman man),此時(shí)如果執(zhí)行a方法傳入man或者h(yuǎn)uman鸽斟,則都將調(diào)用a(Human human)方法拔创,而不會(huì)調(diào)用另外兩個(gè),這是因?yàn)橹剌d方法確認(rèn)時(shí)是根據(jù)左側(cè)的靜態(tài)變量確定調(diào)用哪個(gè)方法的富蓄。但是對(duì)于本例的重寫(xiě)方法:
1:在操作數(shù)棧中壓入Human man = new Man()對(duì)應(yīng)的指令(其實(shí)是三個(gè)剩燥,申請(qǐng)堆內(nèi)存、實(shí)例化立倍、賦值)
2:同1灭红,創(chuàng)建Human man = new Woman()
3:從操作數(shù)棧確定靜態(tài)變量man對(duì)應(yīng)的實(shí)際用例,然后執(zhí)行實(shí)際用例的sayHello()方法帐萎,再將執(zhí)行sayHello()對(duì)應(yīng)指令壓入棧
4:同3步驟比伏。
這就是多態(tài)在運(yùn)行時(shí)的動(dòng)態(tài)鏈接過(guò)程,此過(guò)程無(wú)法在類(lèi)的解析階段進(jìn)行疆导,只能在運(yùn)行時(shí)確定赁项。
以上是java虛擬機(jī)一書(shū)中的解釋。其實(shí)解析階段就是對(duì)類(lèi)或接口中的信息的解析澈段,解析后從類(lèi)或接口中獲取字段或方法的直接引用悠菜,然后進(jìn)行權(quán)限校驗(yàn)
初始化
初始化發(fā)生在加載、驗(yàn)證败富、準(zhǔn)備悔醋、解析之后。在準(zhǔn)備階段會(huì)完成靜態(tài)成員變量的賦初始值操作兽叮,而初始化階段則是完成其賦值和靜態(tài)代碼塊的執(zhí)行芬骄,執(zhí)行發(fā)生在構(gòu)造器函數(shù)(并非構(gòu)造方法)中。
類(lèi)構(gòu)造器
1)類(lèi)構(gòu)造器由編譯器生成鹦聪,其中包含靜態(tài)成員變量的賦值和靜態(tài)代碼塊的執(zhí)行账阻。順序和其在源文件中出現(xiàn)的順序一致,靜態(tài)代碼塊可以訪問(wèn)和賦值出現(xiàn)在其之前的靜態(tài)成員變量泽本,對(duì)之后的靜態(tài)成員變量只能賦值不能訪問(wèn)
2)虛擬機(jī)會(huì)保證父類(lèi)的類(lèi)構(gòu)造器一定發(fā)生在子類(lèi)構(gòu)造器開(kāi)始執(zhí)行之前執(zhí)行完畢
3)類(lèi)構(gòu)造器對(duì)于類(lèi)或接口并不是必須的淘太,如果類(lèi)中沒(méi)有靜態(tài)成員變量或靜態(tài)語(yǔ)句塊兒,接口中沒(méi)有成員變量规丽,則在類(lèi)和接口中并不會(huì)生成類(lèi)構(gòu)造器
4)接口的類(lèi)構(gòu)造器執(zhí)行之前并不要求父接口構(gòu)造器先執(zhí)行蒲牧。只有當(dāng)父接口中的成員變量被訪問(wèn)時(shí),父接口才會(huì)被初始化赌莺,即使接口的實(shí)現(xiàn)類(lèi)在初始化時(shí)也不會(huì)執(zhí)行父接口的類(lèi)構(gòu)造器的執(zhí)行
5)虛擬機(jī)會(huì)保證類(lèi)的類(lèi)構(gòu)造器方法在執(zhí)行時(shí)冰抢,多線程情況下的同步。也就是說(shuō)只有一個(gè)線程可以完成類(lèi)構(gòu)造器的執(zhí)行艘狭,其他線程都要阻塞晒屎,直到構(gòu)造器方法執(zhí)行完成喘蟆。因此在構(gòu)造器方法中不能執(zhí)行耗時(shí)操作,即靜態(tài)代碼塊的耗時(shí)操作
觸發(fā)初始化只有四個(gè)條件:
1)new 創(chuàng)建對(duì)象鼓鲁;訪問(wèn)類(lèi)的靜態(tài)成員變量、訪問(wèn)類(lèi)的靜態(tài)方法
2)以全限定名形式通過(guò)反射加載類(lèi)
3)加載類(lèi)時(shí)港谊,如果其父類(lèi)未初始化骇吭,那么先進(jìn)行父類(lèi)的初始化(繼承的接口不符合此邏輯。接口是只有在訪問(wèn)到接口中成員變量時(shí)才會(huì)去加載歧寺,并不會(huì)隨著實(shí)現(xiàn)類(lèi)而加載)
4)application啟動(dòng)時(shí)燥狰,主類(lèi)要進(jìn)行初始化,就是包含main()方法的類(lèi)斜筐。對(duì)于Android來(lái)說(shuō)就是ActivityThread
說(shuō)兩種容易混淆的初始化場(chǎng)景:
靜態(tài)成員變量的調(diào)用
上面的執(zhí)行代碼只會(huì)輸出SuperClass init!龙致,這是因?yàn)閖ava虛擬機(jī)規(guī)定,對(duì)于靜態(tài)成員變量顷链,只會(huì)初始化直接定義它的類(lèi)目代,因此只會(huì)輸出SuperClass init!
常量的訪問(wèn)
執(zhí)行結(jié)果并沒(méi)有輸出ConstClass init!,是因?yàn)樵诰幾g階段ConstClass.HELLOWORLD的值在編譯之后就被存入到NotIntialization的常量池中嗤练,因此編譯之后榛了,ConstClass和NotIntialization就沒(méi)有任何關(guān)系了,因此不會(huì)輸出ConstClass init!