七、虛擬機(jī)類加載機(jī)制

代碼編譯的結(jié)果從本地機(jī)器碼變?yōu)樽止?jié)碼虽抄,是存儲格式發(fā)展的一小步走搁,卻是編程語言發(fā)展的一大步。

一迈窟、類加載的時(shí)機(jī)

類的變態(tài)生命周期圖:

1.加載階段開始的時(shí)機(jī)

??Java虛擬機(jī)規(guī)范中并沒有強(qiáng)制約束何時(shí)開始開始加載階段朱盐。

2.初始化階段開始時(shí)機(jī)

??虛擬機(jī)規(guī)范嚴(yán)格規(guī)定了有且只有5種情況必須立即對類進(jìn)行“初始化”(加載、驗(yàn)證菠隆、準(zhǔn)備階段自然要在此之前開始):
a)遇到new兵琳、getstatic、putstatic或invokestatic這4條字節(jié)碼指令時(shí)骇径,如果類沒有進(jìn)行過初始化躯肌,則需要先觸發(fā)其初始化。這4條指令分別對應(yīng)Java代碼的場景:new實(shí)例化對象破衔、讀取或設(shè)置一個(gè)類的靜態(tài)字段(被final修飾清女、已在編譯期把結(jié)果放入常量池的靜態(tài)字段除外)、調(diào)用一個(gè)類的靜態(tài)方法晰筛。
b)使用java.lang.reflect包的方法對類進(jìn)行反射調(diào)用時(shí)嫡丙,如果類沒有進(jìn)行過初始化,則需先觸發(fā)其初始化读第。
c)當(dāng)初始化一個(gè)類的時(shí)候曙博,如果發(fā)現(xiàn)其父類還沒有進(jìn)行過初始化,則需先觸發(fā)其父類的初始化怜瞒。
d)當(dāng)虛擬機(jī)啟動(dòng)時(shí)父泳,用戶需要指定一個(gè)要執(zhí)行的主類(包含main方法的那個(gè)類),虛擬機(jī)會先初始化這個(gè)主類吴汪。
e)當(dāng)使用JDK1.7的動(dòng)態(tài)語言支持時(shí)惠窄,如果一個(gè)java.lang.invoke.MethodHandle實(shí)例最后的解析結(jié)果是REF_getStatic、REF_putStatic漾橙、REF_invokeStatic的方法句柄杆融,并且這個(gè)方法句柄所對應(yīng)的類沒有進(jìn)行過初始化,則需要先觸發(fā)其初始化霜运。
??以上五種場景中的行為稱為對一個(gè)類進(jìn)行主動(dòng)引用脾歇。除此之外,所有引用類的方式都不會觸發(fā)初始化觉渴,稱為被動(dòng)引用介劫,如:
a)通過子類引用父類的靜態(tài)字段徽惋,不會導(dǎo)致子類初始化案淋,只會觸發(fā)父類的初始化
b)通過數(shù)組定義來引用類,不會觸發(fā)此類的初始化险绘,因數(shù)組的創(chuàng)建動(dòng)作由字節(jié)碼指令newarray觸發(fā)
c)常量在編譯階段會存入調(diào)用類的常量池中踢京,本質(zhì)上并沒有直接引用到定義常量的類誉碴,因此不會觸發(fā)定義常量的類的初始化
??接口的初始化過程:
??接口初始化與類的初始化真正有所區(qū)別是前面講的主動(dòng)初始化中五種的第三種:當(dāng)一個(gè)類初始化時(shí),要求其父類已經(jīng)初始化過了瓣距,但是一個(gè)接口在初始化時(shí)黔帕,并不要求其父接口全部都完成了初始化,只有在真正使用到父接口的時(shí)候(如引用接口中定義的常量)才會初始化蹈丸。

3.其他階段開始時(shí)機(jī)

??為什么說BT呢成黄?因?yàn)閳D是假的。內(nèi)容真實(shí)逻杖,順序扯淡奋岁。
??加載、驗(yàn)證荸百、準(zhǔn)備闻伶、初始化和卸載這5個(gè)階段的順序是確定的,類的加載過程必須按照這種順序按部就班的開始够话,而解析階段則不一定:它在某些情況下可以在初始化階段之后再開始蓝翰,這是為了支持Java語言的運(yùn)行時(shí)綁定(也稱為動(dòng)態(tài)綁定)。注意女嘲,這里說的按部就班的“開始”畜份,而不是按部就班的“進(jìn)行”或“完成”,強(qiáng)調(diào)這點(diǎn)是因?yàn)檫@些階段通常都是互相交叉的混合進(jìn)行的欣尼,通常會在一個(gè)階段執(zhí)行的過程中調(diào)用漂坏、激活另一個(gè)階段。

二媒至、類加載的過程

1.加載

加載階段顶别,虛擬機(jī)完成以下3件事情:
1)通過一個(gè)類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流
2)將這個(gè)字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)
3)在內(nèi)存中生成一個(gè)代表這個(gè)類的java.lang.Class對象,作為方法區(qū)這個(gè)類的各種數(shù)據(jù)的訪問入口
??相對于類加載過程的其他階段拒啰,一個(gè)非數(shù)組類的加載階段(準(zhǔn)確說驯绎,是加載階段中獲取類的二進(jìn)制流的動(dòng)作)是開發(fā)人員可控性最強(qiáng)的,因?yàn)榧虞d階段既可以使用系統(tǒng)提供的引導(dǎo)類加載器來完成谋旦,也可由用戶自定義的類加載器去完成剩失,開發(fā)人員可通過定義自己的類加載器去控制字節(jié)流的獲取方式(即重寫一個(gè)類加載器的loadClass方法)。
??數(shù)組加載階段:數(shù)組類本身不通過類加載器創(chuàng)建册着,它是由Java虛擬機(jī)直接創(chuàng)建的拴孤。但數(shù)組類與類加載器仍然有密切聯(lián)系,因?yàn)閿?shù)組類的元素類型(數(shù)組去掉所有維度的類型)最終是要靠類加載器去創(chuàng)建甲捏,一個(gè)數(shù)組類(下面簡稱C)創(chuàng)建過程遵循以下規(guī)則:
1)如果數(shù)組的組件類型(數(shù)組去掉一個(gè)維度的類型)是引用類型演熟,那就遞歸采用本節(jié)中定義的加載過程去加載這個(gè)組件類型,數(shù)組C將在加載該組件類型的類加載器的類名稱空間上被標(biāo)識(一個(gè)類必須與類加載器一起確定唯一性)。
2)如果數(shù)組的組件類型不是引用類型芒粹,Java虛擬機(jī)將會把數(shù)組C標(biāo)記為與引導(dǎo)類加載器關(guān)聯(lián)兄纺。
3)數(shù)組類的可見性與它的組件類型的可見性一致,如果組件類型不是引用類型化漆,那數(shù)組類的可見性將默認(rèn)為public估脆。
??對HotSpot虛擬機(jī)而言,java.lang.Class對象比較特殊座云,它雖然是對象疙赠,但是存放在方法區(qū)里面,這個(gè)對象將作為程序訪問方法區(qū)中的這些類型數(shù)據(jù)的外部接口朦拖。

2.驗(yàn)證

??驗(yàn)證階段主要目的是保證輸入的字節(jié)流能正確的解析并存儲于方法區(qū)之內(nèi)棺聊,格式上符合描述一個(gè)Java類型信息的要求。這個(gè)階段的驗(yàn)證是基于二進(jìn)制字節(jié)流進(jìn)行的贞谓,只有通過了這個(gè)階段的驗(yàn)證后限佩,字節(jié)流才會進(jìn)入內(nèi)存的方法區(qū)中進(jìn)行存儲,后面的三個(gè)階段都是計(jì)劃于方法區(qū)的存儲結(jié)構(gòu)進(jìn)行的裸弦,不會再直接操作字節(jié)流祟同。

3.準(zhǔn)備階段

??正式為類變量分配內(nèi)存并設(shè)置類變量初始值的階段,這些變量所使用的內(nèi)存都將在方法區(qū)中進(jìn)行分配理疙。區(qū)分兩個(gè)概念:
1)此時(shí)進(jìn)行分配的僅僅是類變量晕城,而不包括實(shí)例變量,實(shí)例變量將會在對象實(shí)例化時(shí)隨著對象一起分配在Java堆中窖贤;
2)此時(shí)的初始值是指數(shù)據(jù)類型的零值砖顷,而不是Java代碼所初始化的值。特殊情況:如果類字段的字段屬性表中存在ConstantValue屬性赃梧,則在準(zhǔn)備階段變量變量就會被初始化為ConstantValue屬性指定的值滤蝠。

4.解析

??解析階段是虛擬機(jī)將常量池中的符號引用替換為直接引用的過程。符號引用于虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局無關(guān)授嘀,引用的目標(biāo)并不一定已經(jīng)加載到內(nèi)存中物咳。直接引用可以是直接指向目標(biāo)的指針、相對偏移量或是一個(gè)能間接定位到目標(biāo)的句柄蹄皱。直接引用是和虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局相關(guān)的览闰,同一個(gè)符號引用在不同虛擬機(jī)實(shí)例上翻譯出來的直接引用一般會不同。如果有了直接引用巷折,那引用的目標(biāo)一定已經(jīng)在內(nèi)存中存在压鉴。
??虛擬機(jī)實(shí)現(xiàn)可以根據(jù)需要來判斷到底是在類被加載器加載時(shí)就懟常量池中的符號引用進(jìn)行解析,還是等到一個(gè)符號引用將要被使用前采取解析它锻拘。
??對同一個(gè)符號引用進(jìn)行多次解析請求是常見的事情油吭,除了invokedynamic(必須等到程序運(yùn)行到這條指令時(shí),解析動(dòng)作才能進(jìn)行)指令外,虛擬機(jī)實(shí)現(xiàn)可對第一次解析的結(jié)果進(jìn)行緩存(在運(yùn)行時(shí)常量池中記錄直接引用上鞠,并把常量標(biāo)識為已解析狀態(tài))從而避免解析動(dòng)作重復(fù)執(zhí)行际邻。

5.初始化

??類初始化階段是類加載過程的最后一步芯丧,前面的類加載過程中芍阎,除了在加載階段用戶應(yīng)用程序可以通過自定義類加載器參與外,其余動(dòng)作完全由虛擬機(jī)主導(dǎo)和控制缨恒。到了初始化階段谴咸,才真正開始執(zhí)行類中定義的Java程序代碼。初始化階段是執(zhí)行<clinit>()方法的過程骗露。
1)<clinit>()怎么來的
??由編譯器自動(dòng)收集類中的所有類變量的賦值動(dòng)作和靜態(tài)語句塊(static{}塊)中的語句合并產(chǎn)生的岭佳,收集順序是由語句在源文件中出現(xiàn)的順序決定的,靜態(tài)語句塊只能訪問到定義在靜態(tài)語句塊之前的變量萧锉,定義在它之后的變量珊随,在前面的靜態(tài)語句塊可以賦值,但是不能訪問柿隙。

public class Test{
      static{
            i = 1;                              //給變量賦值會通過編譯
            System.out.print(i);        //這句編譯會提示“非法向前引用”
      }
      static int i = 2;
}

??如果類或接口中沒有靜態(tài)語句塊叶洞,也沒有對類變量的賦值操作,那么編譯器可以不為這個(gè)類生成<clinit>()禀崖。
2)<clinit>()與實(shí)例構(gòu)造器<init>()區(qū)別
??<clinit>()不需要顯示調(diào)用父類構(gòu)造器衩辟,虛擬機(jī)會保證在子類的<clinit>()執(zhí)行前,父類的<clinit>()已經(jīng)執(zhí)行完畢波附。因此在虛擬機(jī)中第一個(gè)被執(zhí)行的<clinit>()方法的類肯定是java.lang.Object艺晴。由于父類的<clinit>()先執(zhí)行,意味著父類中定義的靜態(tài)語句塊要優(yōu)先于子類的變量賦值操作掸屡。

static class Parent{
      public static int A = 1;
      static{
            A = 2;
      }
}

static class Sub extends Parent{
      public static int B = A;
}

public static void main(String args[]){
      System.out.println(Sub.B);        //B值為2而不是1
}

3)接口中與類中的<clinit>()的區(qū)別
??接口中不能使用靜態(tài)語句塊封寞,但仍然有類變量初始化的賦值操作,因此也會生成<clinit>()仅财。區(qū)別是:執(zhí)行接口的<clinit>()不需要先執(zhí)行父接口的<clinit>()钥星。只有當(dāng)父接口中定義的變量使用時(shí),父接口才會初始化满着。另外谦炒,接口的實(shí)現(xiàn)類在初始化時(shí)也一樣不會執(zhí)行接口的<clinit>()。
4)<clinit>()的多線程問題
??虛擬機(jī)會保證一個(gè)類的<clinit>()在多線程環(huán)境中被掙錢加鎖风喇、同步宁改。

三、類加載器

??虛擬機(jī)設(shè)計(jì)團(tuán)隊(duì)把加載階段中的“通過一個(gè)類的全限定名來獲取描述此類的二進(jìn)制字節(jié)流”這個(gè)動(dòng)作Java虛擬機(jī)外部去實(shí)現(xiàn)魂莫,以便讓應(yīng)用程序自己決定如何去獲取所需要的類还蹲,實(shí)現(xiàn)這個(gè)動(dòng)作的代碼模塊稱為“類加載器”。

1.類與類加載器

??對任意一個(gè)類,都需要由加載它的類加載器和這個(gè)類本身一同確立其在Java虛擬機(jī)中的唯一性谜喊,每一個(gè)類加載器潭兽,都擁有一個(gè)獨(dú)立的類名稱空間。即要比較兩個(gè)類是否相等斗遏,只有在這兩個(gè)類是由同一個(gè)類加載加載的前提下才有意義山卦。這里所說的“相等”,包括代表類的Class對象的equals()方法和isAssignableFrom()方法诵次、isInstance()方法的返回結(jié)果账蓉,也包括instanceof關(guān)鍵字作對象所屬關(guān)系判斷等情況。

2.雙親委派模型

??從Java虛擬機(jī)角度來講逾一,只存在兩種不同的類加載器:一種是啟動(dòng)類加載器(BootStrap ClassLoader)铸本,這個(gè)類加載器使用C++語言實(shí)現(xiàn),是虛擬機(jī)自身的一部分遵堵;另一種就是所有其他的類加載器箱玷,這些類加載器都是由Java語言實(shí)現(xiàn)的,獨(dú)立于虛擬機(jī)外部陌宿,并且都繼承自抽象類java.lang.ClassLoader锡足。
1)雙親委派模型圖

2)加載器之間關(guān)系
??雙親委派模型要求除了頂層的父類加載器外,其余的類加載器都應(yīng)當(dāng)有自己的父類加載器限番。這里類加載器之間的父子關(guān)系一般不會以繼承的關(guān)系來實(shí)現(xiàn)舱污,而是都使用組合關(guān)系來復(fù)用父加載器的代碼。
3)運(yùn)行邏輯
??先自下向上檢查類是否已經(jīng)被加載過弥虐,若沒有加載則調(diào)用父加載器的loadClass()方法扩灯,若父加載器為空則默認(rèn)使用啟動(dòng)類加載器作為父加載器。如果父類加載失敗霜瘪,拋出ClassNotFoundException異常后珠插,再調(diào)用自己的findClass()進(jìn)行加載。即自下向上檢查類是否被加載颖对,自上向下加載類捻撑。
4)破壞雙親委派模型
??如JNDI、JDBC缤底、代碼熱替換(HotSwap)顾患、模塊熱部署(Hot Deployment)等。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末个唧,一起剝皮案震驚了整個(gè)濱河市江解,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌徙歼,老刑警劉巖犁河,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鳖枕,死亡現(xiàn)場離奇詭異,居然都是意外死亡桨螺,警方通過查閱死者的電腦和手機(jī)宾符,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來灭翔,“玉大人魏烫,你說我怎么就攤上這事〔郑” “怎么了则奥?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵考润,是天一觀的道長狭园。 經(jīng)常有香客問我,道長糊治,這世上最難降的妖魔是什么唱矛? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮井辜,結(jié)果婚禮上绎谦,老公的妹妹穿的比我還像新娘。我一直安慰自己粥脚,他們只是感情好窃肠,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著刷允,像睡著了一般冤留。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上树灶,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天纤怒,我揣著相機(jī)與錄音,去河邊找鬼天通。 笑死泊窘,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的像寒。 我是一名探鬼主播烘豹,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼诺祸!你這毒婦竟也來了携悯?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤序臂,失蹤者是張志新(化名)和其女友劉穎蚌卤,沒想到半個(gè)月后实束,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡逊彭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年咸灿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片侮叮。...
    茶點(diǎn)故事閱讀 39,902評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡避矢,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出囊榜,到底是詐尸還是另有隱情审胸,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布卸勺,位于F島的核電站砂沛,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏曙求。R本人自食惡果不足惜碍庵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望悟狱。 院中可真熱鬧静浴,春花似錦、人聲如沸挤渐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽浴麻。三九已至得问,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間白胀,已是汗流浹背椭赋。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留或杠,地道東北人哪怔。 一個(gè)月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像向抢,于是被迫代替她去往敵國和親认境。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評論 2 354

推薦閱讀更多精彩內(nèi)容