相對(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)注明出處。