《深入理解Java虛擬機》筆記之類文件結(jié)構(gòu)與類加載機制

文章作為《深入理解Java虛擬機》讀書筆記,講的可能就沒書本詳細。

一.類文件結(jié)構(gòu)


Java虛擬機多平臺

都是統(tǒng)一使用的程序存儲格式——字節(jié)碼.class文件
任何語言都可以被特定的編譯器編譯為存儲字節(jié)碼的Class文件币绩,Class文件中包含了Java虛擬機指令集和符號表以及若干其他輔助信息参滴。虛擬機并不關(guān)心Class的來源是何種語言。

Class文件結(jié)構(gòu)

Class文件是一組以8位字節(jié)為基礎(chǔ)單位二進制流峦树,各個數(shù)據(jù)項目嚴格按照順序緊湊地排列在Class文件中,中間沒有添加任何分隔符旦事。

任何一個Class文件都對應著唯一一個類或接口的定義信息魁巩,但反過來說,類或接口并不一定都得定義在文件里族檬,類或接口也可以通過類加載器直接生成歪赢。

Class文件格式采用一種類似C語言結(jié)構(gòu)體的偽結(jié)構(gòu)來存儲數(shù)據(jù),包含兩種數(shù)據(jù)類型:無符號數(shù)和表

魔術(shù)與Class文件的版本

Class文件十六進制結(jié)構(gòu)

使用十六進制編譯器WinHex打開任意一個class文件单料,可以看到它的結(jié)構(gòu)埋凯。

  • 前4個字節(jié)0-3表示為魔數(shù):唯一作用是確定這個文件是否為一個能被虛擬機接受的Class文件,值為0XCAFEBABE扫尖,如圖中所示

  • 接著兩位是次版本號4-5白对,這里值為0x0000

  • 接著兩位是主版本號6-7,這里值為0X0033换怖,也就是十進制51甩恼,表明當前JDK版本號在1.7以上。

常量池

緊接著主次版本號之后的是常量池入口沉颂,它是占用Class文件空間最大的數(shù)據(jù)項目之一条摸,同時還是在Class文件中第一個出現(xiàn)的表類型數(shù)據(jù)項目。

由于常量池中常量的數(shù)目是不固定的铸屉,所以在入口需要放置一項U2類型2個字節(jié)的數(shù)據(jù)代表常量池容量計數(shù)值钉蒲。
這個容量計數(shù)是從1開始而不是從0開始。如圖彻坛,常量池容量的16進制數(shù)是0X02ED顷啼,對應十進制749,這就代表常量池中有748項常量昌屉,索引值范圍為 1-749

Paste_Image.png

常量池中主要存放兩大類常量:字面量和符號引用

訪問標志

在常量池結(jié)束之后钙蒙,緊接著的兩個字節(jié)代表訪問標志acces_flags,用于標識一些類或者接口層次的訪問信息间驮,包括這個Class是類還是接口躬厌,是否定義為public等

類索引,父類索引與接口索引集合

  • 類索引:一個U2類型的數(shù)據(jù)竞帽,用來確定這個類的全限定名
  • 父類索引:一個U2類型的數(shù)據(jù)烤咧,用來確定這個類的父類的全限定名偏陪。由于JAVA不允許多重繼承,所以父類索引引用只有一個煮嫌。
  • 接口索引集合:一組U2類型的數(shù)據(jù)集合笛谦,用來描述這個類實現(xiàn)了哪些接口

類索引,父類索引與接口索引集合都按順序排列在訪問標志之后昌阿。

二.類加載機制


類加載的時機

類從被加載到虛擬機內(nèi)存開始饥脑,到卸載出內(nèi)存為止,整個生命周期包括:加載懦冰、驗證灶轰、準備、解析刷钢、初始化笋颤、使用和卸載七個階段。驗證内地,準備伴澄,解析3個階段部分統(tǒng)稱為連接。

其中加載阱缓、驗證非凌、準備、初始化荆针、和卸載這5個階段的順序是確定的敞嗡。

而解析階段不一定:它在某些情況下可以在初始化階段之后再開始,這是為了支持Java的運行時綁定航背。

類加載生命周期

對于初始化階段喉悴,虛擬機嚴格規(guī)定有且只有5種情況必須立即對類進行初始化

  • 遇到new,getstatic,putstatic,invokestatic這4條字節(jié)碼指令時,如果類沒有進行過初始化玖媚,則需要先觸發(fā)其初始化箕肃。
    eg:使用new關(guān)鍵字實例化對象的時候;讀取或者設(shè)置一個類的靜態(tài)字段最盅;調(diào)用一個類的靜態(tài)方法時等

  • 使用java.lang.reflect包對類進行反射調(diào)用的時候

  • 當初始化一個類的時候突雪,如果發(fā)現(xiàn)其父類還沒有初始化起惕,則要先觸發(fā)其父類的初始化

  • 當虛擬機啟動時涡贱,用戶需要指定一個要執(zhí)行的主類時

  • 當使用JDK 1.7動態(tài)語言支持時,如果一個java.lang.invoke.MethodHandle實例最后的解析結(jié)構(gòu)REF_getStatic,REF_putStatic,REF_invokeStatic的方法句柄惹想,并且這個方法句柄所對應的類沒有進行過初始化问词,則需要先觸發(fā)其初始化。

這5種場景中的行為稱為對一個類進行主動引用
所有引用類的方式都不會觸發(fā)初始化嘀粱,稱為被動引用

對于靜態(tài)字段激挪,只有直接定義這個字段的類才會被初始化辰狡,因為通過其子類來引用父類中定義的靜態(tài)字段,只會觸發(fā)父類的初始化而不會觸發(fā)子類的初始化垄分。

被動引用例子

  • 通過子類引用父類的靜態(tài)字段宛篇,不會導致子類初始化

  • 通過數(shù)組定義來引用類,不會觸發(fā)此類的初始化

  • 常量在編譯階段會存入調(diào)用類的常量池中薄湿,本質(zhì)中并沒有直接引用定義常量的類叫倍,因此不會觸發(fā)定義常量的類的初始化。

加載


虛擬機需要完成三件事情

  • 通過一個類的全限定名來獲取定義此類的二進制字節(jié)流

  • 將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu)(方法區(qū)里用來存儲虛擬機加載的類信息)

  • 在內(nèi)存中生成一個代表這個類的java.lang.Class對象豺瘤,作為方法區(qū)這個類的各種數(shù)據(jù)的訪問入口吆倦。這個Class對象將作為程序訪問方法區(qū)中的這些類型數(shù)據(jù)的外部接口

驗證


驗證是連接階段的第一步,這一階段的目的是為了確保Class文件的字節(jié)流中包含的信息符合當前虛擬機的要求坐求,并且不會危害虛擬機自身的安全蚕泽。驗證階段大致會完成下面4個階段的檢驗動作

  • 一.文件格式驗證:是否以魔數(shù)0xCAFEBABE開頭,主次版本是否在處理機處理范圍內(nèi)等

  • 二.元數(shù)據(jù)驗證:對字節(jié)碼描述的信息進行語義分析桥嗤,以保證描述的信息符合JAVA語言規(guī)范的要求须妻,比如是否有父類等

  • 三.字節(jié)碼驗證:這個階段是最復雜的一個階段,主要 目的是通過數(shù)據(jù)流和控制流分析砸逊,確定程序語義是合法的璧南,符合邏輯的。在第二個階段對元數(shù)據(jù)信息中的數(shù)據(jù)類型做完校驗后师逸,這個階段將對類的方法體進行校驗司倚,保證被校驗類的方法在運行時不會做出危害虛擬機安全的事件。

  • 四.符號引用驗證:最后一個階段的校驗發(fā)生在虛擬機將符號引用轉(zhuǎn)化為直接引用的時候篓像,這個轉(zhuǎn)換動作將在連接第三階段——解析階段中發(fā)生动知。符號引用驗證可以看作是對類自身以外的信息進行匹配性校驗,比如校驗 符號引用中通過字符串描述的全限定名是否能找到對應的類员辩;符號引用中的類盒粮,字段,方法的訪問性(public ,private..)是否可以被當前訪問等奠滑。符號引用驗證的目的是確保解析動作能正常執(zhí)行

準備


準備階段是正式為類變量 分配內(nèi)存 并且設(shè)置 類變量初始值 的階段丹皱,這些變量所使用的內(nèi)存都將在方法區(qū)中進行分配。
這里分配內(nèi)存僅包括類變量(被static修飾的變量)宋税,而不包括實例變量摊崭,實例變量將會在對象實例化時隨著對象一起分配在Java堆中。
其次這里所說的初始值是通常情況下的數(shù)據(jù)類型零值

//假設(shè)一個類變量定位為
public static int value = 123;

那么變量valuew在準備階段過后初始值為0而不是123杰赛,因為這時候尚未開始執(zhí)行任何Java方法呢簸,而把value賦值為123的putstatic指令是程序被編譯后,存放于類構(gòu)造器< clinit >()方法中所以把value賦值為123是在初始化階段才會執(zhí)行。

解析


解析階段是虛擬機將常量池內(nèi)的 符號引用 替換為 直接引用 的過程根时。

  • 符號引用:以一組符號來描述所引用的目標瘦赫,符號可以使任何形式的字面量,只要使用時能無歧義地定位到目標中即可蛤迎。符號引用與虛擬機實現(xiàn)的內(nèi)存布局無關(guān)确虱,引用的目標并不一定已經(jīng)加載到內(nèi)存中。

  • 直接引用:可以是直接指向目標的指針替裆,相對偏移量或是一個能間接定位到目標的句柄蝉娜。直接引用是和虛擬機實現(xiàn)的內(nèi)存布局相關(guān)的。如果有了直接引用扎唾,那么引用的目標必定存在內(nèi)存中召川。

初始化


類初始化時類加載的最后一步,前面類加載過程中胸遇,除了加載階段用戶可以通過自定義類加載器參與以外荧呐,其余動作都是虛擬機主導和控制。到了初始化階段纸镊,才是真正執(zhí)行類中定義Java程序代碼倍阐。

準備階段中,變量已經(jīng)賦過一次系統(tǒng)要求的初始值逗威,而在初始化階段峰搪,根據(jù)程序員通過程序制定的主觀計劃初始化類變量。初始化過程其實是執(zhí)行類構(gòu)造器< clinit >()方法的過程凯旭。

< clinit >()方法是由編譯器自動收集類中 所有類變量的賦值動作 和 靜態(tài)語句塊 中的語句合并產(chǎn)生的概耻。收集的順序是按照語句在源文件中出現(xiàn)的順序。靜態(tài)語句塊中只能訪問定義在靜態(tài)語句塊之前的變量罐呼,定義在它之后的變量可以賦值鞠柄,但不能訪問。

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

< clinit >()方法與類構(gòu)造函數(shù)(或者說實例構(gòu)造器< init >())不同厌杜,他不需要顯式地調(diào)用父類構(gòu)造器,虛擬機會保證子類的< clinit >()方法執(zhí)行之前计螺,父類的< clinit >()已經(jīng)執(zhí)行完畢夯尽。

類加載器


通過一個類的全限定名來獲取描述此類的二進制字節(jié)流。

對于任意一個類登馒,都需要由加載它的類加載器和這個類本身一同確立其在Java虛擬機中的唯一性匙握,每一個類加載器,都擁有一個獨立的類名稱空間谊娇。

比較兩個類是否相等肺孤,只有在這兩個類都是同一個類加載器加載的前提下才有意義,否則济欢,即使這兩個類來源于同一個Class文件赠堵,被同一個虛擬機加載,只要加載他們的類加載器不同法褥,那么這兩個類就必定不相等茫叭。

從JAVA虛擬機角度講,只存在兩種不同的類加載器:

一種是啟動類加載器半等,這個類加載器使用C++語言實現(xiàn)揍愁,是虛擬機自身的一部分
另一種就是所有其他的類加載器,這些類加載器使用JAVA語言實現(xiàn)杀饵,獨立于虛擬機外部莽囤,并且全都繼承自抽象類java.lang.ClassLoader

雙親委派模型


絕大部分JAVA程序都會使用到3種系統(tǒng)提供的類加載器。
啟動類加載器切距,擴展類加載器辽剧,應用程序加載器竭翠。

類加載器雙親委派模型為,相互之間為組合關(guān)系

雙親委派模型

工作過程:如果一個類加載器收到了類加載請求,它首先不會自己去嘗試加載這個類谒出,而是把這個請求委派給父類加載器去完成,每一個層次的類加載器都是如此氓仲,因此所有的加載請求最終都應該傳送到頂層的啟動類加載器中朗恳,只有當父加載器反饋自己無法完成這個加載請求時,子加載器才會去嘗試自己去加載蔚叨。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末床蜘,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蔑水,更是在濱河造成了極大的恐慌悄泥,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肤粱,死亡現(xiàn)場離奇詭異弹囚,居然都是意外死亡,警方通過查閱死者的電腦和手機领曼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進店門鸥鹉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人庶骄,你說我怎么就攤上這事毁渗。” “怎么了单刁?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵灸异,是天一觀的道長。 經(jīng)常有香客問我,道長肺樟,這世上最難降的妖魔是什么檐春? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮么伯,結(jié)果婚禮上疟暖,老公的妹妹穿的比我還像新娘。我一直安慰自己田柔,他們只是感情好俐巴,可當我...
    茶點故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著硬爆,像睡著了一般欣舵。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上缀磕,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天邻遏,我揣著相機與錄音,去河邊找鬼虐骑。 笑死准验,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的廷没。 我是一名探鬼主播糊饱,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼颠黎!你這毒婦竟也來了另锋?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤狭归,失蹤者是張志新(化名)和其女友劉穎夭坪,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體过椎,經(jīng)...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡室梅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了疚宇。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片亡鼠。...
    茶點故事閱讀 38,814評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖敷待,靈堂內(nèi)的尸體忽然破棺而出间涵,到底是詐尸還是另有隱情,我是刑警寧澤榜揖,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布勾哩,位于F島的核電站抗蠢,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏思劳。R本人自食惡果不足惜迅矛,卻給世界環(huán)境...
    茶點故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望敢艰。 院中可真熱鬧,春花似錦册赛、人聲如沸钠导。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽牡属。三九已至,卻和暖如春扼睬,著一層夾襖步出監(jiān)牢的瞬間逮栅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工窗宇, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留措伐,地道東北人。 一個月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓军俊,卻偏偏與公主長得像侥加,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子粪躬,可洞房花燭夜當晚...
    茶點故事閱讀 43,728評論 2 351

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