Class文件結(jié)構(gòu)全面解析(上)

什么是Class文件唠粥?

在Java剛剛誕生的時(shí)候就提出了一個(gè)非常著名的口號(hào):“一次編寫胡嘿,到處運(yùn)行蛉艾。(Write Once,Run Anywhere)”衷敌。為了實(shí)現(xiàn)平臺(tái)無(wú)關(guān)性勿侯,各種不同平臺(tái)的虛擬機(jī)都統(tǒng)一使用一種程序儲(chǔ)存格式,就是字節(jié)碼(ByteCode)缴罗。它就以二進(jìn)制字節(jié)流的方式被存放在Class文件中助琐,其中包含了Java虛擬機(jī)指令集和符號(hào)表以及其他輔助信息。

為什么需要了解Class文件結(jié)構(gòu)面氓?

一般對(duì)于數(shù)據(jù)結(jié)構(gòu)的分享難免比較枯燥兵钮,但是了解Class文件結(jié)構(gòu)是了解Java虛擬機(jī)的重要基礎(chǔ)之一。如果想比較深入地了解Java虛擬機(jī)舌界,那么Class文件結(jié)構(gòu)是不能不接觸的掘譬。我會(huì)力求在保證邏輯準(zhǔn)確的基礎(chǔ)上,盡量通俗易懂地分享呻拌,并結(jié)合實(shí)際案例葱轩。

Class文件結(jié)構(gòu)簡(jiǎn)介

Class文件是一組以8位字節(jié)為基礎(chǔ)單位的二進(jìn)制流,各個(gè)數(shù)據(jù)項(xiàng)目嚴(yán)格按照順序準(zhǔn)確地排列在Class文件中藐握,中間沒有任何分隔符靴拱。當(dāng)遇到8位字節(jié)以上的數(shù)據(jù)時(shí),就按照高位在前的方式(最高位字節(jié)在地址最低位猾普、最低位字節(jié)在地址最高位的順序儲(chǔ)存)分割成多個(gè)8位字節(jié)儲(chǔ)存缭嫡。

Class文件格式采用一種類似于C語(yǔ)言結(jié)構(gòu)體的偽結(jié)構(gòu)來(lái)儲(chǔ)存數(shù)據(jù)的,這種偽結(jié)構(gòu)有兩種數(shù)據(jù)類型:無(wú)符號(hào)數(shù)和表抬闷。

無(wú)符號(hào)數(shù)用u1、u2耕突、u4笤成、u8分別代表1個(gè)字節(jié)、2個(gè)字節(jié)眷茁、4個(gè)字節(jié)和8個(gè)字節(jié)的無(wú)符號(hào)數(shù)炕泳,可以用來(lái)描述數(shù)字、索引引用上祈、數(shù)量值或者UTF-8編碼構(gòu)成的字符串值培遵。

表是由多個(gè)無(wú)符號(hào)數(shù)或其他表作為數(shù)據(jù)項(xiàng)構(gòu)成的復(fù)合數(shù)據(jù)類型浙芙,所有的表都習(xí)慣地以“_info”結(jié)尾。表的數(shù)據(jù)結(jié)構(gòu)和樹很類似籽腕,無(wú)符號(hào)數(shù)相當(dāng)于它的葉子節(jié)點(diǎn)嗡呼,其他的表相當(dāng)于它的子節(jié)點(diǎn)。整個(gè)Class文件就本質(zhì)上也是一個(gè)表皇耗,具體結(jié)構(gòu)如下:


image.png

可以發(fā)現(xiàn)南窗,無(wú)論是無(wú)符號(hào)數(shù)還是表,當(dāng)需要描述同一種類型又?jǐn)?shù)量不定的多條數(shù)據(jù)時(shí)郎楼,就會(huì)用一個(gè)前置的計(jì)數(shù)器加幾個(gè)連續(xù)的數(shù)據(jù)項(xiàng)的方式万伤,這個(gè)時(shí)候我們就把這種一系列連續(xù)的某種類型的數(shù)據(jù)叫做這個(gè)類型的集合。

在Class文件中呜袁,無(wú)論是順序還是數(shù)量敌买,甚至是數(shù)據(jù)存儲(chǔ)的字節(jié)序,都必須嚴(yán)格按照上面表格進(jìn)行設(shè)定阶界,哪個(gè)字節(jié)代表什么含義虹钮,長(zhǎng)度是多少,先后順序怎么樣荐操,都不允許改變芜抒。接下來(lái)看一下各個(gè)數(shù)據(jù)項(xiàng)的具體含義。

魔數(shù)

魔數(shù)(Magic Number)是每個(gè)Class文件的前4個(gè)字節(jié)托启,它用來(lái)確定當(dāng)前文件是否是一個(gè)被Java虛擬機(jī)所接受的Class文件宅倒。很多文件存儲(chǔ)標(biāo)準(zhǔn)中都使用了魔數(shù)進(jìn)行身份識(shí)別,比如gif屯耸、jpeg等圖片文件中都有魔數(shù)拐迁。使用魔數(shù)而不使用擴(kuò)展名是出于安全考慮,因?yàn)閿U(kuò)展名更容易被修改疗绣。文件格式制定者可以自主選擇魔數(shù)线召,只要這個(gè)魔數(shù)沒有被廣泛使用又不和其他文件混淆就可以。

Class文件的魔數(shù)是:0xCAFEBABE多矮,這個(gè)魔數(shù)在Java還被稱為“Oak”語(yǔ)言的時(shí)候就確定下來(lái)了缓淹,據(jù)Java開發(fā)小組最初的關(guān)鍵成員Patrick Naughton說(shuō):“我們一直在尋找一些好玩的、容易記憶的東西塔逃,選擇0xCAFEBABE是因?yàn)樗笳髦Х绕放芇eet's Coffee中深受歡迎的Baristas咖啡”讯壶,他們是真的很喜歡喝咖啡啊,可能也預(yù)示著日后“Java”這個(gè)名字的出現(xiàn)湾盗。

為了更快的理解伏蚊,我準(zhǔn)備了一個(gè)實(shí)際案例,一段非常簡(jiǎn)單的Java代碼:

public class OneMoreStudy {
    private int number;

    private int plusOne() {
        return number + 1;
    }
}

使用JDK 1.7把這段代碼編譯成Class文件格粪,用HexEd打開躏吊,就可以到魔數(shù)了氛改,如下圖:


image.png

在接下來(lái)的分享中,也會(huì)經(jīng)常使用這個(gè)Class文件比伏。

次版本號(hào)和主版本號(hào) 緊跟著魔數(shù)的第5和第6個(gè)字節(jié)是次版本號(hào)(Minor Version)胜卤,第7和第8個(gè)字節(jié)是主版本號(hào)(Major Version)。Java的主版本號(hào)是從45開始的凳怨,從JDK 1.1以后每個(gè)JDK大版本發(fā)布主版本號(hào)都加1瑰艘,高版本的JDK向下兼容低版本的Class文件,但不能運(yùn)行更高版本的Class文件肤舞,即使Class文件的格式?jīng)]有發(fā)生任何變化紫新,Java虛擬機(jī)也會(huì)拒絕運(yùn)行超過(guò)其版本號(hào)的Class文件。

再來(lái)看一下之前的Class文件例子:


image.png

表示次版本號(hào)的第5和第6個(gè)字節(jié)值為0x0000李剖,表示主版本號(hào)的第7和第8個(gè)字節(jié)值為0x0033芒率,也就是十進(jìn)制的51,說(shuō)明這個(gè)Class文件可以被JDK 1.7及其以上版本的Java虛擬機(jī)運(yùn)行篙顺。

常量池 緊跟著主版本號(hào)的就是常量池偶芍,它可以理解為Class文件的資源倉(cāng)庫(kù),也是Class文件結(jié)構(gòu)中與其他數(shù)據(jù)項(xiàng)關(guān)聯(lián)最多的數(shù)據(jù)類型德玫。因?yàn)樵诔A砍刂械某A繑?shù)量是不固定的匪蟀,所以首先有一個(gè)u2類型的數(shù)據(jù),表示常量池容量大性咨(constant_pool_count)材彪。

常量池的容量計(jì)數(shù)不是從0開始的,而是從1開始的琴儿,這是因?yàn)?有它的特殊用用途段化,那就是為了表達(dá)在特殊情況下需要表達(dá)“不引用任何一個(gè)常量池項(xiàng)目”的含義。在Class文件結(jié)構(gòu)中只有常量池的容量計(jì)數(shù)是從1開始的造成,對(duì)于其他集合显熏,包括接口索引集合、字段集合晒屎、方法集合等的容量計(jì)數(shù)都是從0開始的喘蟆。

再來(lái)看一下之前的Class文件例子:


image.png

常量池容器計(jì)數(shù)值為0x0013,也就是十進(jìn)制的19鼓鲁,它表示常量池中有18個(gè)常量履肃,索引值范圍從1到18。

常量池中主要存儲(chǔ)兩種常量:字面量(Literal)和符號(hào)引用(Symbolic References)坐桩。字面量比較接近Java語(yǔ)言層面的常量,比如文本字符串封锉、聲明為final的常量值绵跷。符號(hào)引用則是編譯原理層次的概念膘螟,它包括以下三種:

1.類和接口的全限定名

2.字段的名稱和描述符

3.方法的名稱和描述符

常量池中每一個(gè)常量都是一個(gè)表,共有14種不同的常量類型(JDK1.7及之前版本)碾局,每一種類型的表在第一位都有一個(gè)u1類型的標(biāo)志位荆残,具體如下表:


image.png

有個(gè)一個(gè)專門分析Class文件字節(jié)碼的工具javap,我們用它直接看一下之前的Class文件例子里的18個(gè)常量(常量池以外的信息已省略):

E:\>javap -verbose OneMoreStudy
  Compiled from "OneMoreStudy.java"
  minor version: 0
  major version: 51
Constant pool:
   #1 = Methodref     #4.#15     // java/lang/Object."<init>":()V
   #2 = Fieldref      #3.#16     // OneMoreStudy.number:I
   #3 = Class         #17        // OneMoreStudy
   #4 = Class         #18        // java/lang/Object
   #5 = Utf8          number
   #6 = Utf8          I
   #7 = Utf8          <init>
   #8 = Utf8          ()V
   #9 = Utf8          Code
  #10 = Utf8          LineNumberTable
  #11 = Utf8          plusOne
  #12 = Utf8          ()I
  #13 = Utf8          SourceFile
  #14 = Utf8          OneMoreStudy.java
  #15 = NameAndType   #7:#8       // "<init>":()V
  #16 = NameAndType   #5:#6       // number:I
  #17 = Utf8          OneMoreStudy
  #18 = Utf8          java/lang/Object

其中净当,有一些常量好像在代碼里沒有出現(xiàn)過(guò)内斯,如“I”、“”像啼、“Code”俘闯、“LineNumberTable”、“SourceFile”忽冻。它們其實(shí)自動(dòng)生成的真朗,是后面要分享的字段表、方法表僧诚、屬性表引用到的遮婶,用于描述一些不方便使用“固定字節(jié)”進(jìn)行表達(dá)的內(nèi)容。

訪問(wèn)標(biāo)志 緊跟著常量池的2個(gè)字節(jié)表示訪問(wèn)標(biāo)志(access_flags)湖笨,它用于識(shí)別一些類或接口層次的訪問(wèn)信息旗扑,具體見下表:


image.png

其中,ACC_SUPER在JDK 1.0.2之后編譯出來(lái)的Class文件必須為true慈省;ACC_ABSTRACT對(duì)于接口或抽象類來(lái)說(shuō)為true臀防,其他類為false。

之前的例子OneMoreStudy是一個(gè)普通的類辫呻,不是接口清钥、注解或枚舉,只被public修飾放闺,沒有被聲明為final或abstract祟昭,而且是JDK 1.7編譯的,所以只有ACC_PUBLIC和ACC_SUPER為true怖侦,所以它的訪問(wèn)標(biāo)志應(yīng)該是0x0001 | 0x0020 = 0x0021篡悟,如下圖:


image.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市匾寝,隨后出現(xiàn)的幾起案子搬葬,更是在濱河造成了極大的恐慌,老刑警劉巖艳悔,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件急凰,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡猜年,警方通過(guò)查閱死者的電腦和手機(jī)抡锈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門疾忍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人床三,你說(shuō)我怎么就攤上這事一罩。” “怎么了撇簿?”我有些...
    開封第一講書人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵聂渊,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我四瘫,道長(zhǎng)汉嗽,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任莲组,我火速辦了婚禮诊胞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘锹杈。我一直安慰自己撵孤,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開白布竭望。 她就那樣靜靜地躺著邪码,像睡著了一般。 火紅的嫁衣襯著肌膚如雪咬清。 梳的紋絲不亂的頭發(fā)上闭专,一...
    開封第一講書人閱讀 51,182評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音旧烧,去河邊找鬼影钉。 笑死,一個(gè)胖子當(dāng)著我的面吹牛掘剪,可吹牛的內(nèi)容都是我干的平委。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼夺谁,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼廉赔!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起匾鸥,我...
    開封第一講書人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蜡塌,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后勿负,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體馏艾,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了琅摩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片厚者。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖迫吐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情账忘,我是刑警寧澤志膀,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站鳖擒,受9級(jí)特大地震影響溉浙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蒋荚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一戳稽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧期升,春花似錦惊奇、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至容为,卻和暖如春乓序,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背坎背。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工替劈, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人得滤。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓陨献,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親耿戚。 傳聞我的和親對(duì)象是個(gè)殘疾皇子湿故,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353