JVM(筆記)—— Class 類檔案結構的說明(二)

kanseiu發表於2021-01-04

在這裡插入圖片描述
在這裡插入圖片描述

  1. Class檔案的頭4個位元組,是魔數(Magic Number),表示該檔案是可以被Java虛擬機器接受的檔案,內容是 0xCAFEBABE

  2. 第5和第6個位元組是次版本號(Minor Version),第7和第8個位元組是主版本號(Major Version) ,Java的版本號是從45開始的,高版本的JDK能向下相容,但是低版本的JDK不能執行高版本的Class檔案

  3. 第7、第8個位元組表示主版本號, 如 十六進位制的 0034, 表示 16 * 3 + 4 = 52, 表示Java8, 50 是 Java 6

  4. 第9、第10位元組,是常量池入口,表示常量池中常量的個數, 如 0016, 1 * 16 + 6 = 22, 索引值範圍為 1 ~ 21, 表示常量池中有21個常量,不同於其他地方的計數從0開始, 該值的起始值是從1開始

  5. 接下來是常量池中的常量,使用 javap -verbose xxx.class 可以檢視常量池中的內容, 可以看到, 共21個常量,和上面相吻合
    在這裡插入圖片描述
    ● 對照位元組碼, 和 常量池資料型別結構表, 可以一個個的分析
    在這裡插入圖片描述
    在這裡插入圖片描述
    在這裡插入圖片描述
    ● 分析如下

    ● #1: 第11位元組, tag = 0A = 10, 表示 CONSTANT_Methodref_info 結構, 接下來的u2位元組,即第12、13位元組為 00 04, 表示常量池索引值 = 4的utf8常量; 再下面的u2位元組, 即 第14、15位元組為 00 12 = 18, 表示常量池索引值 = 18的utf8常量;

    ● #2: 第16位元組, tag = 09 = 9, 表示 CONSTANT_Fieldref_info 結構,接下來的兩個u2位元組,即00 03 、00 13,分別表示 常量池索引值 = 3的utf8常量和常量池索引值 = 19的utf8常量

    ● #3: 第21位元組, tag = 07 = 7, 表示 CONSTANT_Class_info 結構,接下來的一個u2位元組,即 00 14, 表示 常量池索引值 = 20 的utf8常量

    ● #4: 第24位元組, tag = 07 =7, 表示 CONSTANT_Class_info 結構,接下來的一個u2位元組,即 00 15, 表示 常量池索引值 = 21 的utf8常量

    ● #5: 第27位元組, tag = 01 = 1, 表示 CONSTANT_Utf8_info 結構,接下來的一個u2位元組, 即00 01, 表示 UTF-8編碼的字串長度 = 1位元組, 因此,後面的一個位元組6D,
    即為此CONSTANT_Utf8_info 結構常量的值, 即 “m”

    ● #6: 第31位元組, tag = 01 = 1, 表示 CONSTANT_Utf8_info 結構,接下來的一個u2位元組, 即00 01, 表示 UTF-8編碼的字串長度 = 1位元組, 因此,後面的一個位元組49,
    即為此CONSTANT_Utf8_info 結構常量的值, 即 “I”

    ● #7: 第35位元組, tag = 01 = 1, 表示 CONSTANT_Utf8_info 結構,接下來的一個u2位元組, 即 00 06, 表示 UTF-8編碼的字串長度 = 6位元組, 因此, 後面的6個位元組
    3C 69 6E 69 74 3E, 即為此CONSTANT_Utf8_info 結構常量的值, 即 “”

    ● #8-#17同理

    ● #18: 第167位元組, tag = 0C = 12, 表示 CONSTANT_NameAndType_info 結構, 接下來的一個u2位元組,即 00 07, 表示 常量池索引值 = 7 的utf8常量, 即該欄位或方法名稱常量為 “”, 之後的一個u2位元組, 即 00 08, 表示 常量池索引值 = 8 的utf8常量, 即 該欄位或方法名稱的描述符為 “()V”

    ● #19: 同#18同理

    ● #20-#21同#5-#17, 到第215位元組

    ● 以下表示從常量池入口 00 16開始,到常量池結尾(第21個常量結束)的二進位制碼

00 16 0A 00 04 00 12 09 00 03 00 13 07 00 14 07 00 15 01 00 01 6D 01 00 01 49 01 00 06 3C 69 6E 69 74 3E 01 00 03 28 29 56 01 00 04 43 6F 64 65 01 00 0F 4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65 01 00 12 4C 6F 63 61 6C 56 61 72 69 61 62 6C 65 54 61 62 6C 65 01 00 04 74 68 69 73 01 00 13 4C 4A 76 6D 54 65 73 74 2F 54 65 73 74 43 6C 61 73 73 3B 01 00 03 69 6E 63 01 00 03 28 29 49 01 00 0A 53 6F 75 72 63 65 46 69 6C 65 01 00 0E 54 65 73 74 43 6C 61 73 73 2E 6A 61 76 61 0C 00 07 00 08 0C 00 05 00 06 01 00 11 4A 76 6D 54 65 73 74 2F 54 65 73 74 43 6C 61 73 73 01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74

在這裡插入圖片描述

  1. 常量池結束後,接下來的2個位元組,即216、217位元組, 00 21, 表示訪問標識位access_flag. access_flags中一共有16個標誌位可以使用,當前只定義了其中9個,沒有使用到的標誌位要求一 律為零。

    ● TestClass是一個普通Java類,不是介面、列舉、註解或者模 塊,被public關鍵字修飾但沒有被宣告為final和abstract,並且它使用了JDK 1.2之後的編譯器進行 編 譯,因此它的ACC_PUBLIC、ACC_SUPER標誌應當為真,而ACC_FINAL、ACC_INTERFACE、 ACC_ABSTRACT、ACC_SYNTHETIC、ACC_ANNOTATION、 ACC_ENUM、ACC_MODULE這七 個標誌應當為假,因此它的access_flags的值應為:0x0001|0x0020=0x0021。

  2. 類索引、父類索引、介面索引集合

    ●類索引: 繼 access_flag 的2個位元組後, 接下來的一個u2位元組, 即第 218、219 位元組(00 03)表示 類索引, 00 03 = 3, 表示 常量池索引值 = 3的utf8常量, 由3號索引找到第20號索引, 即 JvmTest/TestClass

    ● 父類索引: 類索引之後的2個位元組, 第220、221位元組(00 04), 表示 常量池索引值 = 4的utf8常量, 由4號索引找到第21號索引,即java/lang/Object

    ● 介面索引集合: 父類索引之後的2個位元組, 即第222、223位元組(00 00)表示介面索引集合的大小, 為0
    在這裡插入圖片描述

  3. 欄位表集合(這裡是 fields: 1)
    在這裡插入圖片描述
    ● 介面索引之後是欄位表集合

    ● fields_count: 欄位數量, u2位元組, 即 224、225位元組, 00 01, 表示 fields_count = 1

    ● access_flags: 欄位訪問標誌, u2位元組, 第226、227位元組, 00 02, 代表private修飾符的ACC_PRIVATE 標誌位為真(ACC_PRIVAT E標誌的值為0x0002),其他修飾符為假

    在這裡插入圖片描述

    ● name_index: 欄位名稱, u2位元組, 第228、229位元組, 00 05, 是常量池中常量的索引值, 即#5, 由上可知, 欄位名稱為 m

    ● descriptor_index: 欄位描述符, u2位元組, 第230、231位元組, 00 06, 是常量池中常量的索引值, 即#6, 由上可知, 欄位描述符為 I, 即int型

    ● attributes_count: 欄位的屬性個數, u2位元組, 第232、233位元組, 00 00, 表示 屬性個數為0

    ● 根據這些資訊,我們可以推斷出原始碼定義的欄位為“private int m;”

  4. 方法表集合(這裡是 methods: 2)
    在這裡插入圖片描述
    ● access_flags種類

    在這裡插入圖片描述
    ● 方法表集合和欄位表集合類似, 都包括 methods_count、 access_flags、name_index、descriptor_index、attributes_count(方法的屬性個數)、attributes(屬性,
    內容是常量池中常量的索引)

    ● 欄位表集合後, 就是方法表集合

    ● methods_count: 方法個數, u2位元組, 第234、235位元組, 00 02, 表示 方法個數為2, 其中,第一個方法為構造方法, 第二個為物件方法 inc()

    ● access_flags: 方法訪問標誌, u2位元組, 第236、237位元組, 00 01, 表示 ACC_PUBLIC, 方法是public的

    ● name_index: 方法名稱, u2位元組, 第238、239位元組, 00 07, 是常量池中常量的索引值, 即#7, 由上可知, 方法名稱是 “”

    ● descriptor_index: 方法描述符, u2位元組, 第240、241位元組, 00 08, 是常量池中常量的索引值, 即#8, 由上可知, 方法描述符為 ()V, 即引數為空, 無返回值, 符合無參構造

    ● attributes_count: 方法的屬性個數, u2位元組, 第242、243位元組, 00 01, 表示 屬性個數為1

    ● attributes: 方法的屬性, 由上可知, 個數為1個,因此佔2個位元組, 即第244、245位元組, 00 09, 是常量池中常量的索引值, 即#9, 由上可知, 屬性為 “code”

    ● 如果存在屬性, 則接下來會是屬性的內容

  5. 屬性表集合(這裡是 attributes: 1)

    ● 這裡以code屬性為例

    ● code屬性表的結構如下
    在這裡插入圖片描述
    ● attribute_name_index: 屬性名稱索引, u2位元組, 即第244、245位元組, 00 09, 是常量池中常量的索引值, 即#9, 由上可知, 屬性為 “code”

    ● attribute_length: 屬性值的長度, u4位元組, 即第246、247、248、249位元組, 00 00 00 2F, 值為 2 * 16 + 15 = 47, 表示 “code” 屬性的大小為47位元組

    ● max_stack: 運算元棧(Operand Stack)深度的最大值 , u2位元組, 即第250、251位元組, 00 01, 值為1

    ● max_locals(locals): 區域性變數表所需的儲存空間, u2位元組, 即第252、253位元組, 00 01, 值為1

    ※ 注意, 這裡的區域性變數數量為1,即無參構造中存在一個區域性變數
    
    ※ 因為,在任何例項方法裡面,都可以通過“this”關鍵字訪問到此方 法所屬的物件 
    
    ※ 在例項方法的區域性變數表中至少會存在一個指向當前物件例項的區域性變數,區域性變數表中也會預留出第一個變數槽位來存放物件例項的引用,所以例項方法引數值從1開始計算。這個處理只對例項方法有效,同理,()I 代表的inc()方法也是
    

    ● code_length: Java源程式編譯後生成的位元組碼指令的長度, u4位元組, 即第254、255、256、257位元組, 00 00 00 05, 值為5, 表示位元組碼指令的長度為5個位元組

    ● 雖然它是一個u4型別的長度值,理論上最大值可以達到2的32次冪,但是《Java虛擬機器規範》中明確限制了一個方法不允許超過65535條位元組碼指令,即它 實際只使用了u2的長度,如果超過這個限制,Javac編譯器就會拒絕編譯。一般來講,編寫Java程式碼時 只要不是刻意去編寫一個超級長的方法來為難編譯器,是不太可能超過這個最大值的限制的。但是, 某些特殊情況,例如在編譯一個很複雜的JSP檔案時,某些JSP編譯器會把JSP內容和頁面輸出的資訊歸 並於一個方法之中,就有可能因為方法生成位元組碼超長的原因而導致編譯失敗.

    ● code: Java源程式編譯後生成的位元組碼指令, 由上可知, 長度為5個位元組, 即第258、259、260、261、262位元組, 即位元組碼指令為: “2A B7 00 01 B1”

相關文章