《深入理解Java虛擬機(jī)》——類文件結(jié)構(gòu)之魔數(shù)常量池

相對(duì)于Java虛擬機(jī)的其他部分婴洼,這部分的內(nèi)容我們只需要搞清楚下面兩個(gè)方面的內(nèi)容:

1.無(wú)關(guān)性

2.Class文件的結(jié)構(gòu)與組成

我們都知道Java有個(gè)特性是:一次編寫昧诱,到處運(yùn)行猜扮。這里體現(xiàn)的是平臺(tái)無(wú)關(guān)性淮腾,但是對(duì)于Java虛擬機(jī)來(lái)說(shuō)昌犹,不僅僅是具有平臺(tái)無(wú)關(guān)性的特點(diǎn)坚芜,還具有語(yǔ)言無(wú)關(guān)性的特性。

各種不同平臺(tái)的虛擬機(jī)與所有平臺(tái)都統(tǒng)一使用的程序存儲(chǔ)格式——字節(jié)碼(ByteCode)是構(gòu)成平臺(tái)無(wú)關(guān)性的基石斜姥。實(shí)現(xiàn)語(yǔ)言無(wú)關(guān)性的基礎(chǔ)仍然是虛擬機(jī)和字節(jié)碼存儲(chǔ)格式鸿竖。Java虛擬機(jī)不和包括Java在內(nèi)的任何語(yǔ)言綁定,它只與“Class文件”這種特定的二進(jìn)制文件格式所關(guān)聯(lián)铸敏,Class文件中包含了Java虛擬機(jī)指令集和符號(hào)表以及若干其他輔助信息缚忧。基于安全方面的考慮杈笔,Java虛擬機(jī)規(guī)范要求在Class文件中使用許多強(qiáng)制性的語(yǔ)法和結(jié)構(gòu)化約束闪水,但任一門功能性語(yǔ)言都可以表示為一個(gè)能被Java虛擬機(jī)所接受的有效的Class文件。作為一個(gè)通用的蒙具、機(jī)器無(wú)關(guān)的執(zhí)行平臺(tái)球榆,任何其他語(yǔ)言的實(shí)現(xiàn)者都可以將Java虛擬機(jī)作為語(yǔ)言的產(chǎn)品交付媒介。如圖所示:

另外需要注意的是Java語(yǔ)言中的各種變量禁筏、關(guān)鍵字和運(yùn)算符號(hào)的語(yǔ)義最終都是由多條字節(jié)碼命令組合而成的持钉,因此字節(jié)碼命令所能提供的語(yǔ)義描述能力肯定會(huì)比Java語(yǔ)言本身更加強(qiáng)大。因此融师,有一些Java語(yǔ)言本身無(wú)法有效支持的語(yǔ)言特性不代表字節(jié)碼本身無(wú)法有效支持右钾,這也為其他語(yǔ)言實(shí)現(xiàn)一些有別于Java的語(yǔ)言特性提供了基礎(chǔ)蚁吝。

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

Class文件是一組以8位字節(jié)為基礎(chǔ)單位的二進(jìn)制流旱爆,各個(gè)數(shù)據(jù)項(xiàng)目嚴(yán)格按照順序緊湊地排列在Class文件之中舀射,中間沒有添加任何分隔符,這使得整個(gè)Class文件中存儲(chǔ)的內(nèi)容幾乎全部是程序運(yùn)行的必要數(shù)據(jù)怀伦,沒有空隙存在脆烟。當(dāng)遇到需要占用8位字節(jié)以上空間的數(shù)據(jù)項(xiàng)時(shí),則會(huì)按照高位在前的方式分割成若干個(gè)8位字節(jié)進(jìn)行存儲(chǔ)房待。

根據(jù)Java虛擬機(jī)規(guī)范的規(guī)定邢羔,Class文件格式采用一種類似于C語(yǔ)言結(jié)構(gòu)體的偽結(jié)構(gòu)來(lái)存儲(chǔ)數(shù)據(jù),這種偽結(jié)構(gòu)中只有兩種數(shù)據(jù)類型:無(wú)符號(hào)數(shù)和表桑孩,后面的解析都要以這兩種數(shù)據(jù)類型為基礎(chǔ)拜鹤。

無(wú)符號(hào)數(shù)屬于基本的數(shù)據(jù)類型,以u(píng)1流椒、u2敏簿、u4、u8來(lái)分別代表1個(gè)字節(jié)宣虾、2個(gè)字節(jié)惯裕、4個(gè)字節(jié)和8?jìng)€(gè)字節(jié)的無(wú)符號(hào)數(shù),無(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é)尾握玛。表用于描述有層次關(guān)系的復(fù)合結(jié)構(gòu)的數(shù)據(jù),整個(gè)Class文件本質(zhì)上就是一張表甫菠。

無(wú)論是符號(hào)數(shù)還是表败许,當(dāng)需要描述同一類型但數(shù)量不定的多個(gè)數(shù)據(jù)時(shí),經(jīng)常會(huì)使用一個(gè)前置的容量計(jì)算器加若干個(gè)連續(xù)的數(shù)據(jù)項(xiàng)的形式淑蔚,這時(shí)稱這一系列連續(xù)的某一類型的數(shù)據(jù)為某一類型的集合市殷。這里需要注意的是:Class的結(jié)構(gòu)不像XML等描述語(yǔ)言,由于它沒有任何分隔符號(hào)刹衫,所以在上表中醋寝,無(wú)論是順序還是數(shù)量,甚至于數(shù)據(jù)存儲(chǔ)的字節(jié)序(Byte Ordering,Class文件中字節(jié)序?yàn)锽ig-Endian)這樣的細(xì)節(jié)带迟,都是被嚴(yán)格限定的音羞,哪個(gè)字節(jié)代表什么含義,長(zhǎng)度是多少仓犬,先后順序如何嗅绰,都不允許改變。

為了理解清楚Class文件結(jié)構(gòu),這里我跟隨書的例子來(lái)闡述(該Demo使用jdk1.7編譯輸出):

package com.general.class_structure;

public class TestClass {

private int m;

public int inc(){

return m+1;

}

}

接著給出Class文件以及十六進(jìn)制的文件:

1.魔數(shù)

每個(gè)Class文件的頭4個(gè)字節(jié)稱為魔數(shù)(Magic Number)窘面,它的唯一作用是確定這個(gè)文件是否為一個(gè)能被虛擬機(jī)接受的Class文件翠语。很多文件存儲(chǔ)標(biāo)準(zhǔn)中都使用魔數(shù)來(lái)進(jìn)行身份識(shí)別,使用魔數(shù)而不是擴(kuò)展名來(lái)進(jìn)行識(shí)別主要是基于安全方面的考慮财边,因?yàn)槲募U(kuò)展名可以隨意地改動(dòng)肌括。文件格式的制定者可以自由地選擇魔數(shù)值,只要這個(gè)魔數(shù)值還沒有被廣泛采用過(guò)同時(shí)又不會(huì)引起混淆即可酣难。Class文件的魔數(shù)值為0xCAFEBABE谍夭。緊接著魔數(shù)的4個(gè)字節(jié)存儲(chǔ)的是Class文件的版本號(hào):第5個(gè)和第6個(gè)字節(jié)是次版本號(hào)(Minor Version),第7和第8?jìng)€(gè)字節(jié)是主版本號(hào)(Major Version)憨募。Java的版本號(hào)是從45開始的紧索,JDK1.1之后的每個(gè)JDK大版本發(fā)布主版本號(hào)向上加1(JDK1.0~1.1使用了45.0~45.3的版本號(hào)),高版本的JDK能向下兼容以前版本的Class文件菜谣,但不能運(yùn)行以后版本的Class文件齐板,即使文件格式并未發(fā)生任何變化,虛擬機(jī)也必須拒絕執(zhí)行超過(guò)其版本號(hào)的Class文件葛菇。如上圖中前8?jìng)€(gè)字節(jié):CA FE BA BE 00 00 00 33

2.常量池

緊接著主次版本號(hào)之后的是常量池入口甘磨,常量池可以理解為Class文件之中的資源倉(cāng)庫(kù),它是Class文件結(jié)構(gòu)中與其他項(xiàng)目關(guān)聯(lián)最多的數(shù)據(jù)類型眯停,也是占用Class文件空間最大的數(shù)據(jù)項(xiàng)目之一济舆,同時(shí)它還是在Class文件中第一個(gè)出現(xiàn)的表類型數(shù)據(jù)項(xiàng)目。由于常量池中常量的數(shù)量是不固定的莺债,所以在常量池的入口需要放置一項(xiàng)u2類型的數(shù)據(jù)滋觉,代表常量池容量計(jì)數(shù)值(constant_pool_count)。與Java中語(yǔ)言習(xí)慣不一樣的是齐邦,這個(gè)容量計(jì)數(shù)是從1而不是0開始的椎侠,在Class文件格式規(guī)范制定之時(shí),設(shè)計(jì)者將第0項(xiàng)常量空出來(lái)是有特殊考慮的措拇,這樣做的目的在于滿足后面某些指向常量池的索引值的數(shù)據(jù)在特定情況下需要表達(dá)“不引用任何一個(gè)常量池項(xiàng)目”的含義我纪,這種情況就可以把索引值置為0來(lái)表示。Class文件結(jié)構(gòu)中只有常量池的容量計(jì)數(shù)是從1開始丐吓,對(duì)于其他集合類型浅悉,包括接口索引集合、字段表集合券犁、方法表集合等的容量計(jì)數(shù)都與一般習(xí)慣相同术健,是從0開始的。

常量池中主要存放兩大類常量:字面量(Literal)和符號(hào)引用(Symbolic References)粘衬。

字面量比較接近于Java語(yǔ)言層面的常量概念荞估,如文本字符串咳促、聲明為final的常量值等。而符號(hào)引用則屬于編譯原理方面的概念勘伺,主要包括了下面三類常量:

類和接口的全限定名(Fully Qualified Name)

字段的名稱和描述符(Descriptor)

方法的名稱和描述符

Java代碼在進(jìn)行Javac編譯的時(shí)候跪腹,并不像C和C++那樣有“連接”這一步驟,而是在虛擬機(jī)加載Class文件的時(shí)候進(jìn)行動(dòng)態(tài)連接娇昙。也就是說(shuō)尺迂,在Class文件中不會(huì)保存各個(gè)方法笤妙、字段的最終內(nèi)存布局信息冒掌,因此這些字段、方法的符號(hào)引用不經(jīng)過(guò)運(yùn)行期轉(zhuǎn)換的話無(wú)法得到真正的內(nèi)存入口地址蹲盘,也就無(wú)法直接被虛擬機(jī)使用股毫。當(dāng)虛擬機(jī)運(yùn)行時(shí),需要從常量池獲得對(duì)應(yīng)的符號(hào)引用召衔,再在類創(chuàng)建時(shí)或運(yùn)行時(shí)解析铃诬、翻譯到具體的內(nèi)存地址之中。

常量池中每一項(xiàng)常量都是一個(gè)表苍凛,在JDK1.7之前共有11種結(jié)構(gòu)各不相同的表結(jié)構(gòu)數(shù)據(jù)趣席,在jdk1.7中為了更好地支持動(dòng)態(tài)語(yǔ)言調(diào)用,又額外增加了3種(CONSTANT_MethodHandle_info醇蝴、CONSTANT_MethodType_info和CONSTANT_InvokeDynamic_info)宣肚。這14種表都有一個(gè)共同的特點(diǎn),就是表開始的第一位是一個(gè)u1類型的標(biāo)志位悠栓,代表當(dāng)前這個(gè)常量屬于哪種常量類型霉涨。這14種常量類型所代表的具體含義見下圖:

之所以說(shuō)常量池是最煩瑣的數(shù)據(jù),是因?yàn)檫@14種常量類型各自均有自己的結(jié)構(gòu)惭适。

對(duì)于常量池的內(nèi)容大概就這么多笙瑟,下面我們結(jié)合之前給出的demo來(lái)解析一下上文中的16進(jìn)制字節(jié)碼,以便更好的理解類文件中的常量池以及類文件結(jié)構(gòu)癞志,另外需要說(shuō)明一點(diǎn)的是這里所說(shuō)的類均指xxx.class文件而不是xxx.java文件往枷。

0700 02010025636f 6d2f 6765 6e65 7261 6c2f 636c 6173?735f 7374 7275 6374 7572 652f 5465 7374?436c 6173 73

07與01就是指上面所說(shuō)的常量類型。

0700 02代表了一個(gè)類型為CONSTANT_Class_info的常量凄杯,大家可以在上面找出這種常量的結(jié)構(gòu)师溅,其中0002代表指向常量池中的第二個(gè)常量,即后面的010025....617373

010025636f 6d2f 6765 6e65 7261 6c2f 636c 6173?735f 7374 7275 6374 7572 652f 5465 7374?436c 6173 73代表了一個(gè)類型為CONSTANT_Utf8_info的常量盾舌,01代表常量類型墓臭,0025是指字符串長(zhǎng)度,636f......617373的值為:com/general/class_structure/TestClass妖谴。

自己人工解讀16進(jìn)制的字節(jié)碼文件總是有點(diǎn)自虐的窿锉,還好jdk給我們提供了javap工具來(lái)解讀類文件酌摇,比如我們的例子,在命令行下輸入相應(yīng)命令:

javap -v xxx.class輸出的信息如下:

xxxxxxx/com/general/class_structure$ javap-vTestClass.class

Classfilexxxxxx/TestJVMDemo/bin/com/general/class_structure/TestClass.class

Lastmodified2017-10-11;size409bytes

MD5 checksum4f90a8afb819eac2dcef42a2b02ae603

Compiledfrom"TestClass.java"

publicclasscom.general.class_structure.TestClass

SourceFile:"TestClass.java"

minor version:0

major version:51

flags:ACC_PUBLIC,ACC_SUPER

Constantpool:

#1=Class#2// ?com/general/class_structure/TestClass

#2=Utf8com/general/class_structure/TestClass

#3=Class#4// ?java/lang/Object

#4=Utf8java/lang/Object

#5=Utf8m

#6=Utf8I

#7=Utf8

#8=Utf8()V

#9=Utf8Code

#10=Methodref#3.#11// ?java/lang/Object."":()V

#11=NameAndType#7:#8// ?"":()V

#12=Utf8LineNumberTable

#13=Utf8LocalVariableTable

#14=Utf8this

#15=Utf8Lcom/general/class_structure/TestClass;

#16=Utf8inc

#17=Utf8()I

#18=Fieldref#1.#19// ?com/general/class_structure/TestClass.m:I

#19=NameAndType#5:#6// ?m:I

#20=Utf8SourceFile

#21=Utf8TestClass.java

{

publiccom.general.class_structure.TestClass();

flags:ACC_PUBLIC

Code:

stack=1,locals=1,args_size=1

0:aload_0

1:invokespecial#10// Method java/lang/Object."":()V

4:return

LineNumberTable:

line3:0

LocalVariableTable:

StartLengthSlotNameSignature

050thisLcom/general/class_structure/TestClass;

publicintinc();

flags:ACC_PUBLIC

Code:

stack=2,locals=1,args_size=1

0:aload_0

1:getfield#18// Field m:I

4:iconst_1

5:iadd

6:ireturn

LineNumberTable:

line6:0

LocalVariableTable:

StartLengthSlotNameSignature

070thisLcom/general/class_structure/TestClass;

}

大家可以在上面的輸出信息中清楚地看到嗡载,常量池的個(gè)數(shù)窑多、類型,值洼滚。大家看到上面的常量池也許會(huì)懵逼埂息,我們前面講了常量池包含字面量和符號(hào)引用,字面量可以理解為Java語(yǔ)言層面的常量遥巴,而符號(hào)引用包含三類常量:(1)類和接口的全限定名(2)字段的名稱和描述符(3)方法的名稱和描述符千康。常量池中的

、LineNumberTable铲掐、I等這些是什么鬼拾弃,代碼里我們沒有寫啊,其實(shí)這部分常量是自動(dòng)生成的摆霉,會(huì)在后面的字段表豪椿、方法表、屬性表引用到携栋。

最后說(shuō)一下javap搭盾,javap是java class文件分解器,可以反編譯(即對(duì)javac編譯的文件進(jìn)行反編譯)婉支,也可以查看java編譯器生成的字節(jié)碼鸯隅。用于分解class文件。

為了避免篇幅過(guò)長(zhǎng)磅摹,關(guān)于類文件結(jié)構(gòu)的相關(guān)總結(jié)不打算寫在一篇文章里了滋迈,這篇文章主要說(shuō)明了無(wú)關(guān)性、魔數(shù)户誓、常量池饼灿。接下來(lái)的文章會(huì)繼續(xù)介紹Class文件結(jié)構(gòu)中的其他內(nèi)容。

轉(zhuǎn)載請(qǐng)注明出處:http://blog.csdn.net/android_jiangjun/article/details/78204427

作者:GeneralAndroid

鏈接:http://www.reibang.com/p/8f327bb334e6

來(lái)源:簡(jiǎn)書

著作權(quán)歸作者所有帝美。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán)碍彭,非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末悼潭,一起剝皮案震驚了整個(gè)濱河市庇忌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌舰褪,老刑警劉巖皆疹,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異占拍,居然都是意外死亡略就,警方通過(guò)查閱死者的電腦和手機(jī)捎迫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)表牢,“玉大人窄绒,你說(shuō)我怎么就攤上這事〈扌耍” “怎么了彰导?”我有些...
    開封第一講書人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)敲茄。 經(jīng)常有香客問(wèn)我位谋,道長(zhǎng),這世上最難降的妖魔是什么折汞? 我笑而不...
    開封第一講書人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任倔幼,我火速辦了婚禮盖腿,結(jié)果婚禮上爽待,老公的妹妹穿的比我還像新娘。我一直安慰自己翩腐,他們只是感情好鸟款,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著茂卦,像睡著了一般何什。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上等龙,一...
    開封第一講書人閱讀 49,079評(píng)論 1 285
  • 那天处渣,我揣著相機(jī)與錄音,去河邊找鬼蛛砰。 笑死罐栈,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的泥畅。 我是一名探鬼主播荠诬,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼位仁!你這毒婦竟也來(lái)了柑贞?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤聂抢,失蹤者是張志新(化名)和其女友劉穎钧嘶,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體琳疏,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡有决,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年摄欲,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片疮薇。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡胸墙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出按咒,到底是詐尸還是另有隱情迟隅,我是刑警寧澤,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布励七,位于F島的核電站智袭,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏掠抬。R本人自食惡果不足惜吼野,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望两波。 院中可真熱鬧瞳步,春花似錦、人聲如沸腰奋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)劣坊。三九已至嘀倒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間局冰,已是汗流浹背测蘑。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留康二,地道東北人碳胳。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像赠摇,于是被迫代替她去往敵國(guó)和親固逗。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345

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

  • 很遺憾藕帜,這將是很枯燥的一章烫罩,但是如果想較為深入的理解JVM,這一章又很有必要硬著頭皮搞清楚洽故。如果之前沒有接觸過(guò)類似...
    蝦_米_閱讀 623評(píng)論 1 4
  • 字節(jié)碼查看工具:WinHex 前言 Java虛擬機(jī)實(shí)現(xiàn)語(yǔ)言無(wú)關(guān)性的基石就是Class文件Java虛擬機(jī)提供的語(yǔ)言無(wú)...
    zlcook閱讀 7,121評(píng)論 4 18
  • 《深入理解Java虛擬機(jī)》筆記_第一遍 先取看完這本書(JVM)后必須掌握的部分贝攒。 第一部分 走近 Java 從傳...
    xiaogmail閱讀 5,062評(píng)論 1 34
  • 駕車途徑秦東收費(fèi)站——港口——華陰,下了高速时甚,左轉(zhuǎn)直行隘弊,就到了目的地——“華山北站”哈踱。 下車,父親打開后備箱梨熙,提起...
    阿介與城閱讀 255評(píng)論 0 0
  • 本來(lái)寫了一點(diǎn)點(diǎn)別的开镣,覺得不是很重要,就刪掉了咽扇。 今天很不錯(cuò)啊邪财,就是早上起來(lái)不想說(shuō)話,但夜里的夢(mèng)是很好玩的质欲。 下了一...
    先叫這個(gè)名字吧閱讀 171評(píng)論 0 0