1. 程式人生 > 其它 >JVM--Class檔案結構

JVM--Class檔案結構

一、程式碼示例

  後面的程式碼舉例都已如下程式碼示例

package org.fenixsoft.clazz;
public class TestClass {
    private int m;
    public int inc() {
        return m + 1;
    }
}

  編譯後的class檔案,使用16進位制文字開啟(我使用的是UItraEdit),內容如下:

    

二、class檔案結構簡述

  class檔案由魔數、版本號(副版本號、主版本號)、常量池資訊(常量池計數器和常量池資料區)、訪問標誌、類索引(當前類索引、父類索引)、介面資料區(介面數量、介面資訊)、方法資料區(方法數量、方法資訊)、屬性資料區(屬性數量、屬性資訊組成)組成。

  具體的內容如下表所示,無符號數使用u1、u2、u4、u8分別代表1、2、4、8個位元組,如果有多個無符號組成的表,則以 _info 結尾表示(有符號可以類比為java中的簡單基本資料型別,而有多個無符號組成的表可以類比為java中的一個物件,物件中都是基本資料型別的屬性)

    

  結構如下所示:

      

三、魔數與版本資訊

  1、魔數:由上面的表格可以看到,魔數佔用4個位元組,其值固定為CAFEBABY,與示例中的值一樣

  2、副版本號(次版本號):佔用2個位元組,目前沒有使用,為 00 00

  3、主版本號:佔用兩個位元組。JAVA的其實版本號為45,即JDK1的版本號為45,每一個大版本號加一;高版本jdk可以執行低版本jdk編譯的class檔案,反之則不可以。以上面的示例程式碼為例,其版本為十六進位制的0x0034,即十進位制的52,那麼編譯class檔案的jdk版本則為jkd8(52-45+1)

四、常量池資料區

(一)常量池儲存資訊  

  緊跟著主次版本號後面的就是常量池資訊。

    

  常量值中主要存放兩類資訊:字面量和符號引用。

    字面量比較接近java中常量的意思,如文字字串、被final修飾的常量等;

    而符號引用則屬於編譯原理方面的概念,主要包括:

      1、被模組到處或者開放的包

      2、類和介面的全限定名

      3、欄位的名稱和描述符

      4、方法控制代碼和方法型別

      5、動態呼叫點和動態常量

  最早的常量表中有11種結構不相同的表結構資料,後來為了支援動態語言呼叫,加入了4種,後來又為了支援模組化,又加入了兩種,因此至JDK13,共有17種表結構資料。

  每一種表結構資料最少有兩部分組成,tag和info,其中tag是用來標記不同表結構的,info用來表示具體的資訊。具體的info資訊,可以看最後的常量表結構。

(二)int和float常量儲存結構  

  首先對於對於基本資料型別和String的值對應的info資訊是具體的值,例如int和float的結構如下所示:tag分別為3和4,然後使用4個位元組來表示值。

    

(三)long和double常量儲存結構  

  對於long和double型別,tag分別為5和6,並且使用8個位元組儲存其值,其中前4個位元組為高位數,後4個位元組為低位數。

    

(四)String常量儲存結構  

  對於String型別的資料,tag為8,info為兩個位元組的字串索引項(也就是下面要說的utf8格式的cp_info)

    

(五)utf8常量儲存結構  

  上面String的info資訊即是對一個UTF8格式索引項,那麼utf8格式的常量,tag為1,然後使用兩個位元組儲存utf8編碼位元組陣列的長度,然後使用具體的長度的陣列儲存資料。

    

  對於String儲存具體是怎麼樣的,可以參照下圖,就是定義了一個String,其字串為“JVM原理”,那麼JVM原理是儲存在utf8格式的cp_info中,在utf8的cp_info中,儲存了tag,長度,及具體的字串內容;然後在String的cp_info中,只是儲存了對於utf8的cp_info的索引項。

    

(六)類儲存結構  

  而對於類、方法、屬性的cp_info則是由類或介面的索引項和型別名稱的索引項。

  以類為例,其tag為7,index為佔用兩個位元組的類名稱索引項,對應的也是一個utf格式的cp_info

    

  常量表詳情:

常量名 描述 專案(無符號數) 型別 描述
CONSTANT_Utf8_info utf8編碼的字串 tag u1 值為1
length u2 utf8的字串佔用了多少位元組數
bytes u1 長度為length的utf8編碼的字串
CONSTANT_Integer_info 整型字面量 tag u1 值為3
bytes u4 按照高位在前儲存的int值
CONSTANT_Float_info 浮點數字面量 tag u1 值為4
bytes u4 按照高位在前儲存的float值
CONSTANT_Long_info 長整型字面量 tag u1 值為5
bytes u8 按照高位在前儲存的long值
CONSTANT_Double_info 雙精度浮點型字面量 tag u1 值為6
bytes u8 按照高位在前儲存的double值
CONSTANT_Class_info 類或介面的符號引用 tag u1 值為7
index u2 指向全限定名常量項的索引
CONSTANT_String_info 字串型別字面量 tag u1 值為8
index u2 指向字串字面量的索引
CONSTANT_Fieldref_info 欄位的符號引用 tag u1 值為9
index u2 指向欄位宣告類或介面描述符CONSTANT_Class_info的索引
index u2 指向欄位描述符CONSTANT_NameAndType的索引
CONSTANT_Methodref_info 類中方法的符號引用 tag u1 值為10
index u2 指向方法宣告類描述符CONSTANT_Class_info的索引。
index u2 指向名稱及型別描述符CONSTANT_NameAndType的索引
CONSTANT_InterfaceMethodref_info 介面中方法的引用 tag u1 值為11
index u2 指向方法宣告介面描述符CONSTANT_Class_info的索引
index u2 指向名稱及型別描述符CONSTANT_NameAndType的索引
CONSTANT_NameAndType_info 欄位或方法的部分符號引用 tag u1 值為12
index u2 指向該欄位或方法名稱常量項的索引
index u2 指向該欄位或方法描述符常量項的索引
CONSTANT_MethodHandle_info 表示方法控制代碼 tag u1 值為15
reference_kind u1 值必須在1-9之間,閉區間,它決定了方法控制代碼的型別,方法控制代碼型別值表示方法控制代碼的位元組碼行為
reference_index u2 值必須是對常量池的索引
CONSTANT_MethodType_info 表示方法型別 tag u1 值為16
descroptor_index u2 值必須是對常量池的有效索引,常量池在該索引處的項必須是CONSTANT_Utf8_info
CONSTANT_Dynamic_info 表示一個動態計算常量 tag u1 值為17
bootstrap_method_attrindex u2 值必須是當前class檔案中引導方法表的bootstrap_method[]陣列的有效索引
name_and_type_index u2 值必須是對當前常量池的有效索引,常量池在該索引處的項必須是CONSTANT_NameAndType_info結構,表示方法名和方法描述符
CONSTANT_InvokeDynamic_info 表示一個動態方法呼叫點 tag u1 值為18
bootstrap_method_attrindex u2 值必須是當前class檔案中引導方法表的bootstrap_method[]陣列的有效索引
name_and_type_index u2 值必須是對當前常量池的有效索引,常量池在該索引處的項必須是CONSTANT_NameAndType_info結構,表示方法名和方法描述符
CONSTANT_Module_info 表示一個模組 tag u1 值為19
name_index u2 值必須是對當前常量池的有效索引,常量池在該索引處的項必須是CONSTANT_Utf8_info結構,表示模組名稱
CONSTANT_Package_info 表示一個模組中開放或者匯出的包 tag u1 值為20
name_index u2 值必須是對當前常量池的有效索引,常量池在該索引處的項必須是CONSTANT_Utf8_info結構,表示模組名稱

(七)哪些字面量會進入常量池

  結論:

    1、final修飾的8種基本資料型別都會進入常量池

    2、非final修飾的8種基本資料型別,只有double、long、float的值會進入常量池。

    3、常量池種包含的字串型別字面量(雙引號引起來的字串值)

五、訪問標誌和類索引(當前類索引、父類索引)

  1、訪問標誌

  在常量池結束以後,緊接著兩個位元組表示訪問標誌,這個標誌用於識別一些類或者介面層次的訪問資訊,例如是否為public、是否為abstract、如果是類的話是否被宣告為final等等,具體標誌位及含義如下所示

    

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

  類索引和父類索引都是一個u2型別的資料,介面索引集合是一組u2型別的資料集合,class檔案中由這三項資料來確定該型別的繼承關係。類索引用於確定這個類的全限定名,父類索引用於確定這個類父類的全限定名。介面索引集合用來描述這個類實現了哪些介面。

六、欄位表集合和放發表集合

(一)欄位表集合 

  欄位表集合用於描述介面或者類中宣告的變數。其包含類變數(static修飾)和例項變數。

  欄位可以包括的修飾符有欄位的作用域(public、private、protected修飾符)、是例項變數還是類變數(static修飾符)、可變性(final)、併發可見性(volatile修飾符,是否強制從主記憶體讀寫)、可否被序列化(transient修飾符)、欄位資料型別(基本型別、物件、陣列)、欄位名稱。

  

  欄位修飾符放在access_flags專案中,它與類中的access_flags專案是非常類似的,都是一個u2的資料型別。

  

  跟隨access_flags標誌的是兩項索引值:name_index和descriptor_index。它們都是對常量池項的引用,分別代表著欄位的簡單名稱以及欄位和方法的描述符。

  基本資料型別(byte、char、double、float、int、long、short、boolean)以及代表無返回值的void型別都用一個大寫字元來表示,而物件型別則用字元L加物件的全限定名來表示

  

  舉例說明

  

(二)方法表集合

  這裡跟上面的欄位表很類似,就不再多說,直接上表

  

  

(三)屬性表集合

  屬性表(attribute_info),Class檔案、欄位表、方法表都可以攜帶自己的屬性表集合,以描述某些場景專有的資訊。

  

    

七、javap命令

  可以使用javap命令反編譯class檔案,例如使用javap命令反編譯示例的class檔案:javap -v TestClass

      

  從輸出結果看,首先是警告,然後是檔案地址、最後修改時間、檔案大小、MD5加密、包名、次版本號、主版本號、訪問標誌等內容。

  然後就是常量池內容:

    1、第一個內容是方法的cp_info,備註裡面可以看到,這個方法名是init方法,其有兩個索引項,方法對應的類或介面和方法的NameAndType,其分別是4和15

        先來看4,說明其是一個類,從備註可以看到該類是Object類,其對應18的索引項,指向了Object類;

        再來看15,其是方法的NameAndType,分別對應7和8,7說明該方法名稱是init方法,8說明該方法返回值為void

    2、第二個內容是欄位的cp_info,存在對應類或介面的索引項和欄位的NameAndType,分別對應3和16

        先來看3,其是一個類,有全限定名的索引項,指向了17,再看17,其類為TestClass類。

        再來看16,是一個NameAndType的索引項,其中包含了名稱和型別的索引項,對應5和6,5說明了該欄位名稱為m,6說明了該欄位的型別是int

    3、再往下就是code區,及code區中方法資訊。

    4、然後看具體的init方法,其標識了方法的返回值、訪問標誌、棧深等資訊,然後就是具體的操作,使用aload將1壓進棧,再使用getfield將獲取m,然後使用iconst將m壓進棧,然後使用iadd相加,最後使用ireturn返回結果。