《深入理解Java虛擬機》讀書筆記3--類文件結(jié)構(gòu)

很遺憾捺氢,這將是很枯燥的一章惜姐,但是如果想較為深入的理解JVM,這一章又很有必要硬著頭皮搞清楚胡岔。如果之前沒有接觸過類似的內(nèi)容法希,那么有很大的可能第一次基本讀不懂,如果出現(xiàn)這樣的情況也沒有關(guān)系姐军,請繼續(xù)保持學習,并且隔段時間再次重新閱讀尖淘。像我這樣不夠靈光的腦袋奕锌,學習了3遍也就能夠掌握基本原理。其實村生,只要掌握了對應的規(guī)則惊暴,類文件的內(nèi)容又是很容易解讀的,請保持你的耐心與好奇

Java號稱跨平臺趁桃,那么究竟是什么能夠使Java跨平臺辽话?簡單來說就是兩點:第一是編譯器能夠?qū)⒃创a編譯成某種平臺無關(guān)的格式;第二是能夠?qū)⒃摲N格式翻譯成具體平臺指令集的虛擬機

而這種平臺無關(guān)的格式就是字節(jié)碼卫病。虛擬機不與包括Java語言在內(nèi)的任何語言綁定油啤,它只與字節(jié)碼關(guān)聯(lián)。因此也就誕生了后續(xù)眾多基于JVM的新型語言

完整的類文件結(jié)構(gòu)說明請參考“官網(wǎng)文檔:The class File Format

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

1.數(shù)據(jù)組織方式:緊湊的二進制

類文件是一組以字節(jié)為基礎(chǔ)的二進制數(shù)據(jù)蟀苛,各數(shù)據(jù)項嚴格按照定義排列益咬,中間沒有分隔符及填充。如果遇到8位以上的數(shù)據(jù)項時帜平,則按照大端法(Big-Endian幽告,關(guān)于大端法可參考“理解字節(jié)序 - 阮一峰的網(wǎng)絡(luò)日志”)拆分成若干個字節(jié)存儲

2.數(shù)據(jù)類型:無符號數(shù)和表

#無符號數(shù)梅鹦,用來描述數(shù)字、索引或者UTF-8編碼的字符串值冗锁。u1齐唆、u2、u4冻河、u8分別代表1個字節(jié)箍邮、2個字節(jié)、4個字節(jié)芋绸、8個字節(jié)的無符號數(shù)

#表媒殉,由多個無符號數(shù)或其他表組合成復合數(shù)據(jù)結(jié)構(gòu)。Class文件本質(zhì)上就是一張表

3.多個同類數(shù)據(jù)項的描述:前置容量

由于類文件不采用分隔符的方式分隔數(shù)據(jù)摔敛,數(shù)據(jù)項的順序是被嚴格限定的廷蓉,因此當需要描述多個同類數(shù)據(jù)項的時候,采用前置容量計數(shù)器的方式

類文件結(jié)構(gòu)定義詳見下圖:

類文件結(jié)構(gòu)詳解

通過上面的講解马昙,我們對類文件結(jié)構(gòu)有了一個宏觀的了解桃犬。接下來,我們通過一個簡單的類文件實例行楞,來深入細節(jié)具體看一下類文件結(jié)構(gòu)

首先攒暇,我們定義一個足夠簡單的Java類,詳見下圖:

之后將該類編譯后子房,通過十六進制方式查看TestClass.class文件形用。看著像亂碼证杭?然而并不是

另外我們還可以通過javap命令田度,查看該文件的反匯編信息

1.魔數(shù)(magic)

魔數(shù)用來描述文件類型,是一個u4類型的數(shù)據(jù)(占據(jù)類文件的頭4個字節(jié))

Java類文件的魔數(shù)值是0xCAFEBABE解愤,看到這個是不是想起了Java的商標(咖啡)

使用魔數(shù)來表示文件類型镇饺,顯然比使用文件擴展名更加安全。虛擬機在讀取到0xCAFEBABE后則認為該文件是一個Class文件

2.版本號(version)

緊接著的4個字節(jié)代表的是類文件的版本號送讲,其中前兩個字節(jié)代表次版本號(Minor Version)奸笤,后兩個字節(jié)代表主版本號(Major Version)。通過版本號哼鬓,虛擬機能夠檢查是否可兼容該類文件

查看十六進制類文件监右,看到次版本號是0x0000(十進制0),主版本號是0x0034(十進制52)异希,我本地使用的編譯器版本是1.8.0秸侣。具體編譯器版本對應的十進制版本號請自行查閱,不在此贅述

3.常量池(constant_pool)

緊接著版本號的是常量池,常量池是類文件中第一個表類型的數(shù)據(jù)味榛,其中數(shù)據(jù)項眾多椭坚,并且數(shù)量不定。前面我們說過搏色,對于描述多個同類數(shù)據(jù)項的時候善茎,采用前置容量計數(shù)器的方式。因此在常量池之前频轿,是一個u2類型的數(shù)據(jù)垂涯,代表常量池容量計數(shù)(constant_pool_count)

查看TestClass類文件,常量池容量計數(shù)值是0x0016(十進制22)航邢,代表該類有21項常量耕赘,索引范圍是1-21(注意從1開始,而不是0)

常量池主要存放兩大類數(shù)據(jù):字面量和符號引用

#字面量比較接近于Java語言層面常量的概念膳殷,比如字符串操骡、聲明為final的常量值等

#符號引用主要包括:類和接口的全限定名、字段的名稱和描述符赚窃、方法的名稱和描述符册招。Java代碼編譯時,沒有“靜態(tài)連接”這一步驟勒极,而是通過“動態(tài)連接”的方式是掰。虛擬機在運行時,從常量池中獲取對應的符號引用辱匿,再翻譯到具體的內(nèi)存地址

常量池中每一各數(shù)據(jù)項都對應一個表键痛,一共有如下這些類型(14種),其中每一項開頭都包含一個u1類型的tag(下圖Value列)匾七,代表當前數(shù)據(jù)項代表的常量數(shù)據(jù)類型

下面我們繼續(xù)使用TestClass作為例子絮短,看看常量池中數(shù)據(jù)是怎樣定義的:

首先我們看到的tag值是0x0a(十進制10),查閱上表乐尊,看到對應的是CONSTANT_Methodref_info戚丸,說明該常數(shù)項是方法的符號引用划址。我們看一下CONSTANT_Methodref_info的數(shù)據(jù)定義:

第一項是tag扔嵌,上面說過了。第二項是class_index夺颤,代表擁有此方法的類的類信息在常量池中的索引痢缎。第三項是name_and_type_index,代表該方法的名稱和描述符信息在常量池中的索引

查看我們的類文件世澜,class_index值是0x0004独旷,說明常量池中第4項存放該類的類信息。name_and_type_index值是0x0012(十進制18),說明常量池中第18項存放名稱和描述符信息

另外嵌洼,從上面提到的反匯編信息中案疲,也可以更加明確地看出我們從十六進制類文件中分析出的內(nèi)容

上面,我們通過查閱CONSTANT_Methodref_info的數(shù)據(jù)定義麻养,并且對照十六進制類文件和反匯編信息褐啡,學習了怎樣讀懂類文件結(jié)構(gòu)中的常量池信息。其實其他類型的常量和CONSTANT_Methodref_info一樣鳖昌,都是類似的結(jié)構(gòu)备畦。下圖中選中的部分就是常量池相關(guān)的數(shù)據(jù),有興趣可以按照上述的方法對照官方文檔逐一進行解析

4.訪問標志(access_flags)

在常量池之后许昨,緊接著的兩個字節(jié)表示訪問標志懂盐。這個標志用于識別一些類或者接口層次的訪問信息。包括:該Class是否是public類型糕档、是否被聲明為final莉恼、是否是一個接口、是否是注解翼岁、是否是枚舉等

完整定義如下:

其中ACC_SUPER代表是否允許invokespecial指令的新語義类垫。invokespecial在JDK 1.0.2版本發(fā)生過改變,因此為了區(qū)分這條指令使用哪種語意琅坡,JDK 1.0.2之后該標志位都為0x0020悉患。對于1.8及以上版本,無論該標志位是否被設(shè)置榆俺,JVM都會統(tǒng)一認為該標志位為真

我們實例中的TestClass售躁,僅被定義為public,并且我當前使用的是1.8版本的JDK茴晋,因此ACC_PUBLIC及ACC_SUPER會被設(shè)置陪捷,其他標志位都為0。最終訪問標志位的值會被設(shè)置為0x0001 | 0x0020 = 0x0021

5.類索引(this_class)诺擅、父類索引(super_class)市袖、接口索引集合(interfaces)

類文件中通過這三項信息來確定這個類的繼承關(guān)系。其中類索引和父類索引都是u2類型的數(shù)據(jù)烁涌,接口索引集合是一組u2類型的數(shù)據(jù)(接口索引前會有一個u2類型數(shù)據(jù)表示接口索引的數(shù)量constant_pool_count)苍碟。這三類數(shù)據(jù)都指向常量池中的某項數(shù)據(jù)

類索引用來確定該類的全限定名认罩;父類索引確定其父類的全限定名躯枢。Java是單繼承,所以父類索引只有一個氓奈;接口索引集合用來描述該類實現(xiàn)了哪些接口

繼續(xù)看我們的TestClass抒钱,類索引值為0x0003(十進制3)蜓肆,說明類信息在常量池的第三項颜凯,結(jié)合反匯編代碼,可以看到“class_structure/TestClass”

父類索引值為0x0004(十進制4)仗扬,說明父類信息在常量池的第四項症概,結(jié)合反匯編代碼,可以看到TestClass繼承自“java/lang/Object”

接口索引的數(shù)量值為0x0000(十進制0)早芭,說明該類并沒有實現(xiàn)任何接口

6.字段表集合(fields)

字段表集合用于描述類中的變量(包括靜態(tài)變量穴豫、實例變量,但不包括局部變量)

每個字段通過一個field_info描述逼友,field_info格式定義如下:

field_info中的access_flags作用及計算方式與類的access_flags類似精肃,詳細定義如下:

access_flags之后是name_index和descriptor_index,分別代表字段的簡單名稱索引及描述符索引帜乞,他們都是對常量池中常量的引用司抱。之后是attributes方面的內(nèi)容,后面再做介紹

下面來看一下TestClass黎烈,fields_count值為0x0001(十進制1)习柠,代表只有一個字段(private int m;);access_flags值為0x0002(十進制2)照棋,對照上面的access_flags定義表资溃,發(fā)現(xiàn)只有ACC_PRIVATE為真,所以值為0x0002烈炭;name_index值為0x0005(十進制5)溶锭,說明字段的簡單名稱引用常量池中第5項;descriptor_index值為0x0006(十進制6)符隙,說明字段的描述符引用常量池中第6項(反匯編代碼常量第6項的“I”代表基本類型int)趴捅;attributes_count值為0x0000(十進制0),說明沒有額外屬性

7.方法表集合(methods)

顧名思義霹疫,方法表集合用于描述類中的方法

如果理解了上一節(jié)的字段表集合拱绑,那么方法表集合就很好理解了,因為method_info在結(jié)構(gòu)上與field_info極其類似丽蝎。每個方法都通過一個method_info來描述

同樣猎拨,第一項是access_flags,詳細定義如下:

后續(xù)幾項:name_index屠阻、descriptor_index红省、attributes含義都與字段表中類似,只不過在方法表中這些字段用于描述方法而已

也許你會有所疑問:方法里面的代碼在哪里栏笆?方法里面的代碼类腮,存放在屬性表集合中一個名為“Code”的屬性里臊泰。關(guān)于屬性表的內(nèi)容蛉加,后面我們再做講解

繼續(xù)回到TestClass蚜枢,methods_count值為0x0002(十進制2),代表有兩個方法(其中一個是編譯器自動添加的實例構(gòu)造器<init>针饥,另一個是我們自己定義的public int inc()方法)厂抽;第一個方法的access_flags值為0x0001(十進制1),對照上面的access_flags定義表丁眼,發(fā)現(xiàn)只有ACC_PUBLIC為真筷凤,所以值為0x0001;name_index值為0x0007(十進制7)苞七,說明方法名稱引用常量池中第5項藐守;descriptor_index值為0x0008(十進制8),說明方法的描述符引用常量池中第8項(反匯編代碼常量池第8項的“()V”代表void方法)蹂风;attributes_count值為0x0001(十進制1)卢厂,說明該方法的屬性表集合有一項屬性,索引為0x0009(十進制9)惠啄,對應常量池中第9項常量為“Code”慎恒,說明此屬性是方法的字節(jié)碼描述

8.屬性表集合(attributes)

前面在講解類文件、字段表撵渡、方法表時曾多次出現(xiàn)屬性表這個概念融柬,它的主要作用是用于描述某些場景下的專有信息

截止到j(luò)ava 8,屬性表集合中一共預定義了23種屬性趋距,下面我們拿一些屬性作為例子進行講解粒氧,完整的介紹請參看官方文檔

對于每個屬性,屬性的名稱(attribute_name_index)引用常量池中的常量节腐,屬性值(info)的結(jié)構(gòu)完全自定義靠欢,只需要一個u4類型的長度屬性(attribute_length)來說明屬性值占用的字節(jié)數(shù)

#Code屬性

前面在介紹方法表的時候曾提到過Code屬性,其用于存儲方法體中編譯后的內(nèi)容铜跑。但并非所有方法表都存在這個屬性门怪,比如接口和抽象類中的抽象方法。Code屬性結(jié)構(gòu)如下:

1)attribute_name_index和attribute_length上面已經(jīng)講過

2)max_stack代表操作數(shù)棧的最大深度锅纺,虛擬機需要根據(jù)這個值來分配棧幀中操作數(shù)棧的深度

3)max_locals代表了局部變量表所需的存儲空間掷空。max_locals的單位是slot,slot是虛擬機為局部變量分配內(nèi)存的最小單位囤锉。對于32位的數(shù)據(jù)類型(byte坦弟、char、short官地、int酿傍、float、boolean驱入、returnAddress)赤炒,每個局部變量占用一個slot氯析,而對于64位的數(shù)據(jù)類型(long、double)則需要占用兩個slot莺褒。另外掩缓,max_locals的值并不是方法中定義了多少個局部變量,就把相應占用的slot數(shù)量簡單相加遵岩。原因在于你辣,當代碼執(zhí)行超出了某個變量的作用域之后,它所占用的slot就可以被其他的局部變量所占用尘执,因此slot實際是可以復用的舍哄。編譯器會根據(jù)作用域給本地變量分配slot,然后計算出max_locals的值

4)code_length和code用來存儲編譯器編譯后的字節(jié)碼指令(類似于匯編指令)誊锭。code_length代表字節(jié)碼的長度蠢熄,code則是一系列字節(jié)碼流。每個字節(jié)碼指令占用一個字節(jié)炉旷,當虛擬機讀取到code中的一個字節(jié)签孔,會根據(jù)字節(jié)碼指令表找到對應的指令,并且可以知道這條指令后面是否會跟隨參數(shù)窘行,以及參數(shù)數(shù)量和具體含義饥追。1個字節(jié)取值范圍是0x00(十進制0)~0xFF(十進制255),也就是說一共可以表示256種指令

下面我們再次通過我們的TestClass來看一下code_length和code是如何定義的罐盔。首先code_length值為0x00000005(十進制5)但绕,說明后續(xù)五個字節(jié)是code

code中第一個字節(jié)值是0x2A,查表得知對應指令為aload_0惶看,該指令含義是將第0個slot中的引用類型的本地變量推入操作數(shù)棧頂

code中第二個字節(jié)值是0xB7捏顺,查表得知對應指令為invokespecial,該指令含義是將操作數(shù)棧頂?shù)囊脭?shù)據(jù)所指向的對象作為方法接收者纬黎,調(diào)用該對象的實例構(gòu)造方法幅骄、private方法或者他父類的方法

code中第三和第四個字節(jié)值是0x0001(十進制1),這個u2類型的數(shù)據(jù)是前面invokespecial指令的參數(shù)本今,它指向常量池中第一個常量拆座,代表具體調(diào)用哪個方法。查看反匯編代碼冠息,可以看到對應的是“java/lang/Object."<init>":()V”挪凑,代表調(diào)用父類Object的實例構(gòu)造方法

code中第五個字節(jié)值是0xB1,查表得知對應指令為return逛艰,含義是從當前方法返回void躏碳,這條指令執(zhí)行后方法結(jié)束

我們再次查看TestClass的反匯編代碼,看到其中兩個方法(實例構(gòu)造方法和inc方法)args_size的值都是1散怖,但是這兩個方法實際上都是無參的肄渗。另外無論是參數(shù)列表還是方法體內(nèi),都沒有定義任何局部變量脸甘,但是locals也都是1沿猜。這是因為橄妆,在實例方法內(nèi),我們可以通過this關(guān)鍵字訪問此方法所屬的對象剃袍,而this正是通過編譯器在方法調(diào)用時通過方法參數(shù)自動傳入的。如果inc方法是static的虱肄,那么args_size就是0了

5)exception_table_length和exception_table用來描述異常處理信息。exception_table中一共包含4項信息御毅,含義是:如果在start_pc到end_pc(不含)位置出現(xiàn)了類型為catch_type(包含其子類)的異常根欧,則轉(zhuǎn)向handler_pc處進行處理

#Exceptions屬性

這里的Exceptions屬性與上面講到的Code屬性里的exception_table不是一回事兒,這里的Exceptions屬性與Code屬性平級端蛆,代表該方法可能拋出的checked異常

Exceptions屬性中的number_of_exceptions表示方法可能拋出的checked異常的數(shù)量凤粗。exception_index_table指向常量池中的常量,表示異常類型

#LineNumberTable屬性

LineNumberTable屬性用于描述源代碼行號與字節(jié)碼偏移量之間的對應關(guān)系今豆。它雖然不是運行時必須的屬性嫌拣,但是如果沒有相應的信息,那么程序拋出異常時呆躲,異常堆棧中將沒有行號亭罪,另外也無法按照源碼行來設(shè)置斷點

#LocalVariableTable屬性

LocalVariableTable屬性用于描述棧幀中局部變量表中變量與源碼中變量的關(guān)系。它也不是運行時必須的屬性歼秽,但是如果沒有相應信息应役,那么當其他人引用方法時,源碼中定義的參數(shù)名稱都將丟失燥筷,取而代之的是類似arg0箩祥、arg1這樣的的占位符

#Signature屬性

Signature屬性出現(xiàn)于類、字段表肆氓、方法表結(jié)構(gòu)的屬性中袍祖,用于JDK1.5之后,記錄范型信息谢揪。之所以加入一個屬性記錄范型信息蕉陋,是因為Java中范型采用的是擦除法實現(xiàn)的偽范型。在字節(jié)碼中拨扶,范型信息會被擦除凳鬓,優(yōu)點是實現(xiàn)簡單(主要修改編譯器,虛擬機很少改動)患民,但缺點就是運行期間無法獲得范型信息缩举。Signature屬性就是為了彌補這個缺陷增設(shè)的

行文至此,我想類文件的結(jié)構(gòu)原理已經(jīng)基本描述清楚了,其余沒有講到的結(jié)構(gòu)也都是類似仅孩,如果有興趣或者日后有需要用到托猩,可以再做詳細的了解

筆記3結(jié)束

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市辽慕,隨后出現(xiàn)的幾起案子京腥,更是在濱河造成了極大的恐慌,老刑警劉巖溅蛉,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件公浪,死亡現(xiàn)場離奇詭異,居然都是意外死亡温艇,警方通過查閱死者的電腦和手機因悲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門堕汞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來勺爱,“玉大人,你說我怎么就攤上這事讯检∷雎常” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵人灼,是天一觀的道長围段。 經(jīng)常有香客問我,道長投放,這世上最難降的妖魔是什么奈泪? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮灸芳,結(jié)果婚禮上涝桅,老公的妹妹穿的比我還像新娘。我一直安慰自己烙样,他們只是感情好冯遂,可當我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著谒获,像睡著了一般蛤肌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上批狱,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天裸准,我揣著相機與錄音,去河邊找鬼赔硫。 笑死狼速,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播向胡,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼恼蓬,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了僵芹?” 一聲冷哼從身側(cè)響起处硬,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎拇派,沒想到半個月后荷辕,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡件豌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年疮方,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片茧彤。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡骡显,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出曾掂,到底是詐尸還是另有隱情惫谤,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布珠洗,位于F島的核電站溜歪,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏许蓖。R本人自食惡果不足惜蝴猪,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望膊爪。 院中可真熱鬧自阱,春花似錦、人聲如沸蚁飒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽淮逻。三九已至琼懊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間爬早,已是汗流浹背哼丈。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留筛严,地道東北人醉旦。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親车胡。 傳聞我的和親對象是個殘疾皇子檬输,可洞房花燭夜當晚...
    茶點故事閱讀 44,781評論 2 354

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