深入理解JVM - 類文件的結構

類文件的結構

Class類文件是以8個字節爲單位的二進制流,由魔數、版本號、常量池、類信息、父類信息、接口表、字段表、方法表和屬性表組成。下圖清晰的展示了Class類文件的結構。
在這裏插入圖片描述

Class類文件示例

預先準備好一段簡單的Java代碼和編譯好的二進制字節流。在這裏插入圖片描述

Class類文件是如何組成的

接下來會用上述簡單的Java代碼爲示例來講解Class類文件是如何組成的?

魔數和版本號

魔數是用來檢查字節流是不是Class類文件,佔4個字節。Class類文件的魔數是cafebabe
查看偏移地址0x00000000-0x00000003,得到魔數是0xcafebabe,可以得出該字節流是一個Class類文件。

版本號是指Jdk的版本號,佔4個字節。前2個字節是次版本號,後2個字節是主版本號。
查看偏移地址0x00000004-0x00000007,得到次版本號是0(=0x0000),主版本號是52(=0x0034),也就是版本號是52.0,對應的是jdk8。

常量池

常量個數指常量池中有多少個常量,佔2個字節。
查看偏移地址0x00000008-0x00000009,得到該常量池中一共有22(=0x17-1)個常量。減1的原因是常量是從1開始計數的。

常量指名稱(類名、字段名、方法名等)或描述符的字面量或符號引用。

  • 字面量可以理解爲不經過翻譯的字符串。比如Java文件中的類名Hello字符串;比如字段名a、b、m字符串;比如方法名inc字符串;比如返回類型int字符串;
  • 符號引用可以理解爲由標籤和索引(指向字面量或標籤)組成。比如類和接口的全限定名;比如字段的名稱和描述符;比如方法的名稱和描述符。

接下來我們看下這22個常量在字節流中是怎樣體現的?查找常量就像密碼學裏根據密碼錶找到對應的密碼一樣。首先準備好定義好的17種數據結構,爲了節省篇幅,這17種數據結構請讀者參考《深入理解Java虛擬機第3版》常量池一節的表6.6。這裏僅列出4種數據結構,下圖右邊部分。
在這裏插入圖片描述
上圖左邊部分已根據定義好的17種數據結構列出了Hello.class的22個常量項(#號開頭的數字),現以第一個常量項爲例來進行講解。

  • 第1號常量項的偏移地址0x0000000A的值爲10(=0x0a),對應的是CONSTANT_Methodref_info標籤。根據該標籤的數據結構可得,一共佔5個字節,第1個字節就是前面說的標籤;第2-3個字節表示該方法屬於哪個類,這裏指向的是第4號常量項;第4-5字節表示該方法的名稱和描述符(指返回類型),這裏指向的是第19號常量項。
  • 第4號常量項的偏移地址0x00000017的值爲7(=0x07),對應的是CONSTANT_Class_info標籤。根據該標籤的數據結構可得,一共佔3個字節,第1個字節就是前面說的標籤;第2-3個字節表示該方法所在類的全限定名,這裏指向的是第22號常量項。
  • 第22號常量項的偏移地址0x0000009F的值爲1(=0x01),對應的是CONSTANT_Utf8_info標籤。根據該標籤的數據結構可得,一共佔6個字節,第1個字節就是前面說的標籤;第2-3個字節表示方法名的長度,這裏的值爲16個字節,接下來的16個字節清晰的展示了類的全限定名(java/lang/Object)。
  • 第19號常量項的偏移地址0x0000008D的值爲12(=0x0c),對應的是CONSTANT_NameAndType_info標籤。根據該標籤的數據結構可得,一共佔5個字節,第1個字節就是前面說的標籤;第2-3個字節表示該方法的名稱,這裏指向的是第11號常量項;第4-5字節表示該方法的描述符,這裏指向的是第12號常量項。
  • 第11號常量項的偏移地址0x0000003F的值爲1(=0x01),對應的是CONSTANT_Utf8_info標籤。與第22號常量項類似,最後清晰的展示了方法名稱(<init> ,即構造器)。
  • 第12號常量項的偏移地址0x00000048的值爲1(=0x01),對應的是CONSTANT_Utf8_info標籤。與第22號常量項類似,最後清晰的展示了方法描述符(()V ,即void)。

最後通過javap命令讓我們更直觀的感受下常量池。在這裏插入圖片描述

類信息和父類信息

類信息由類訪問符類名索引父類名索引組成,他們各佔2個字節。
在這裏插入圖片描述
查看偏移地址0x000000b2-0x000000b3,得到值爲0x0021(=0x0001+0x0020),查表可得類訪問符爲public super。
查看偏移地址0x000000b4-0x000000b5,得到值爲3(=0x0003),表示指向第3號常量項(Hello)。
查看偏移地址0x000000b6-0x000000b7,得到值爲4(=0x0004),表示指向第4號常量項(java/lang/Object)。

接口表

接口數量佔用2個字節。查看偏移地址0x000000b8-0x000000b9,得到值爲0,也就是說該類沒有實現相關接口。
接口表描述了接口相關信息。

字段表

字段數量佔用2個字節。查看偏移地址0x000000bA-0x000000bB,得到值爲3(=0x0003),也就是說該類中有3個字段。
字段表描述了字段相關信息。

接下來我們根據字段表結構來讀下Hello.class字節流中的3個字段。
在這裏插入圖片描述
上圖中已經標示了字節流中的3個字段(^角開頭的數字)。現以第一個字段爲例來進行講解。

  • 偏移地址0x000000bC-0x000000bD的值爲0x001a(=0x0002+0x0008+0x0010),定義了字段的訪問符,查找字段訪問符表可知,該字段由private static final修飾。
  • 偏移地址0x000000bE-0x000000bF的值爲5(=0x0005),定義了字段名稱索引,表示指向第5個常量項(a)
  • 偏移地址0x000000c0-0x000000c1的值爲6(=0x0006),定義了字段描述符索引,表示指向第6個常量項(I,即int)
  • 偏移地址0x000000c2-0x000000cB定義了該字段的屬性,屬性表稍後講解。

方法表

方法數量佔用2個字節。查看偏移地址0x000000dC-0x000000dD,得到值爲2(=0x0002),也就是說該類中有2個方法。
方法表描述了方法相關信息。

接下來我們根據方法表結構來讀下Hello.class字節流中的2個方法。
在這裏插入圖片描述
上圖中已經標示了字節流中的2個方法(^角開頭的數字)。現以第一個方法爲例來進行講解。

  • 偏移地址0x000000dE-0x000000dF的值爲0x0001,定義了方法的訪問符,查找方法訪問符表可知,該方法由public修飾。
  • 偏移地址0x000000e0-0x000000e1的值爲11(=0x000b),定義了方法名稱索引,表示指向第11個常量項(<init> ,即構造器)。
  • 偏移地址0x000000e2-0x000000e3的值爲12(=0x000c),定義了方法描述符索引,表示指向第12個常量項(()V,即void)
  • 偏移地址0x000000e4-0x000000fA定義了該方法的屬性,屬性表稍後講解。

最後通過javap命令讓我們更直觀的感受下方法表。
在這裏插入圖片描述

屬性表

ConstantValue屬性一般定義在字段表中。下圖展示了ConstantValue屬性的數據結構。
在這裏插入圖片描述
在字段表中說過,偏移地址0x000000c2-0x000000cB定義了字段a的屬性。

  • 偏移地址0x000000c2-0x000000c3的值爲1(=0x0001),定義了該字段屬性的個數。
  • 偏移地址0x000000c4-0x000000c5的值爲7(=0x0007),定義了該字段屬性名稱的索引,表示指向第7個常量項(ConstantValue)。
  • 偏移地址0x000000c6-0x000000c9的值爲2(=0x0002),定義了該屬性的長度,表示該屬性一共佔用2個字節。
  • 偏移地址0x000000ca-0x000000cb的值爲8(=0x0008),定義了該屬性的值,表示指向第8個常量項(屬性值爲10)。

Code屬性一般定義在字段表中。下圖展示了Code屬性的數據結構。
在這裏插入圖片描述
在方法表中說過,偏移地址0x000000e4-0x000000fA定義了<init>方法的屬性。

  • 偏移地址0x000000e4-0x000000e5的值爲1(=0x0001),定義了該方法屬性的個數。

  • 偏移地址0x000000e6-0x000000e7的值爲13(=0x000d),定義了該方法屬性名稱的索引,表示指向第13個常量項(Code)。

  • 偏移地址0x000000e8-0x000000eB的值爲29(=0x001d),定義了該屬性的長度,表示該屬性一共佔用29個字節。

  • 偏移地址0x000000eC-0x000000eD的值爲1(=0x0001),定義了該方法操作數棧深度。

  • 偏移地址0x000000eE-0x000000eF的值爲1(=0x0001),定義了該方法局部變量表大小。

  • 偏移地址0x000000f0-0x000000f3的值爲5(=0x0005),定義了方法體的長度。

  • 偏移地址0x000000f4-0x000000f8的值爲0x2ab70001b1,定義了方法體的指令,這些指令都可以在虛擬機字節碼指令表中查到。
    1)讀入2a,查表得0x2a對應的指令爲aload_0,表示將第0個變量槽中reference類型(指this)推送到操作數棧頂。
    2)讀入b7,查表得0xb7對應的指令爲invokespecial,invokespecial用於調用實例構造器、private方法或者它的父類的方法。讀入0001,指向第1個常量項,表示invokespecial調用的是實例構造器。
    3)讀入b1,查表得0xb1對應的指令爲return,表示方法正常結束。

  • 偏移地址0x000000f9-0x0000108,依次定義了異常表、LineNumberTable屬性,這裏不作進一步講解。

爲了節省篇幅,屬性表中只介紹ConstantValue屬性和Code屬性兩種比較重要的屬性,其他的屬性,比如Exceptions屬性、SourceFile屬性等,有興趣的朋友可以閱讀《深入理解Java虛擬機第3版》屬性表一節。

Reference

深入理解Java虛擬機第3版
Java虛擬機原理圖解

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章