JVM學習筆記——Class類檔案解讀

午夜12點發表於2019-02-18

簡述

Java原始碼通過編譯生成.class檔案位元組碼後再被JVM解釋轉化為目標機器程式碼,從而實現一次編寫到處,到處執行(“Write Once,Run Anywhere”)。位元組碼與平臺無關,而且並不是只有Java語言編譯為位元組碼檔案在虛擬機器上執行。

類檔案的結構

Class檔案是一組以8位位元組為基礎單位的二進位制流,各個資料專案嚴格按照順序緊湊地排列在Class檔案中。Class檔案只有兩種資料型別:無符號數和表。

無符號數屬於基本的資料型別,有u1, u2, u4, u8,分別代表1個位元組、2個位元組、4個位元組和8個位元組的無符號數

整個Class檔案就是一張表,由以下資料項構成:

型別 名稱 數量
u4 magic(魔數) 1
u2 minor_version(次版本號) 1
u2 major_version(主版本號) 1
u2 constant_pool_count(常量池容量) 1
cp_info constant_pool(常量池) constant_pool_count-1
u2 access_flags(訪問標誌) 1
u2 this_class(類索引) 1
u2 super_class(父類索引) 1
u2 interfaces_count(介面容量) 1
u2 interfaces(介面) interfaces_count
u2 fields_count(欄位容量) 1
field_info fields(欄位) fields_count
u2 methods_count(方法容量) 1
mehtod_info methods(方法) method_count
u2 attributes_count(屬性容量) 1
attribute attributes(屬性) attributes_count

小試牛刀

寫個簡單實體類,javac編譯後,檢視其位元組碼
原始碼


public class Person {

    private int age;

    public int getAge(){
        return age;
    }

    public static synchronized void work() {
        System.out.println("工作");
    }

    public static void main(String[] args) {

    }
}   
複製程式碼

十六進位制Class檔案

JVM學習筆記——Class類檔案解讀

根據上述資料項表格我們按順序拆分

  • 魔數
  • 魔數站每個Class檔案的頭4個位元組,其作用未確定確定這個檔案是否為一個能被虛擬機器接受的Class檔案
    示例中CA FE BA BE為魔數

  • 版本號
  • 魔數後面緊跟著版本號
    00 00——次版本號
    00 34——主版本號
    根據如下:

    十進位制版本號 主版本
    jdk1.8 52
    jdk1.7 51
    jdk1.6 50
    jdk1.5 49
    jdk1.4 48
    jdk1.3 47
    jdk1.2 46
    jdk1.1 45

    十六進位制0034,對應十進位制52,對應jdk1.8版本

  • 常量池
  • 常量池可以理解為Class檔案之中的資源倉庫,它是Class檔案結構中與其他專案關聯最多的資料型別。常量池中主要存放兩大類常量:字面量和符號引用.

    字面量——接近於Java中的常量概念,eg:final修飾、文字字串
    符號引用——編譯原理概念:類和介面的全限定名、欄位的名稱和描述符、方法的名稱和描述符

    常量池中的每一項常量都是一個表,每種常量都有自己的結構,14種常量含義:

    型別 標誌 描述
    CONSTANT_utf8_info 1 utf-8編碼的字串
    CONSTANT_Integer_info 3 整型字面量
    CONSTANT_Float_info 4 浮點型字面量
    CONSTANT_Long_info 5 長整型字面量
    CONSTANT_Double_info 6 雙精度浮點型字面量
    CONSTANT_Class_info 7 類或者介面的符號引用
    CONSTANT_String_info 8 字串型字面量
    CONSTANT_Fieldref_info 9 欄位的符號引用
    CONSTANT_Methodref_info 10 類中方法的符號引用
    CONSTANT_InterfaceMethoderf_info 11 介面中方法的符號引用
    CONSTANT_NameAndType_info 12 欄位或方法的部分符號引用
    CONSTANT_MethodHandle_info 15 表示方法控制程式碼
    CONSTANT_MethodType_info 16 標識方法型別
    CONSTANT_InvokeDynamic_info 18 表示一個動態方法呼叫點

    0×0029轉十進位制為41,代表常量池中有40項常量(容量計數是從1而不是0開始。第0項常量空出來是表達“不引用任何一個常量池專案”)

    0A即十進位制10,對應表中CONSTANT_Methodref_info,其結構如下:

    型別 名稱 描述
    u1 tag 值為10
    u2 index 指向宣告方法的類描述符CONSTANT_Class_info的索引項
    u2 index 指向名稱及型別描述符CONSTANT_NameAndType_info的索引項

    0×0007為常量池中第7項CONSTANT_Class_info,0×001A為第26項CONSTANT_NameAndType_info。按照《深入理解Java虛擬機器》第二版,172頁中表6-6順序解析得:

    
    00 29                                         //constant_pool_count(常量池容量)
     #1、0A 0007 001A                             //CONSTANT_Methodref_info,#7,#26
     #2、09 0006 001B                             //CONSTANT_Fieldref_info,#6,#27
     #3、09 001C 001D                             //CONSTANT_Fieldref_info,#28,#29
     #4、08 001E                                  //CONSTANT_String_info,#30
     #5、0A 001F 0020                             //CONSTANT_Methodref_info,#31,#32
     #6、07 0021                                  //CONSTANT_Class_info,#33
     #7、07 0022                                  //CONSTANT_Class_info,#34
     #8、01 0003 61 67 65                         //CONSTANT_Utf8_info,3個位元組,age
     #9、01 0001 49                               //CONSTANT_Utf8_info,1個位元組,I
    #10、01 0006 3C 69 6E 69 74 3E                //CONSTANT_Utf8_info,6個位元組,
    #11、01 0003 28 29 56                         //CONSTANT_Utf8_info,3個位元組,()V
    #12、01 0004 43 6F 64 65                      //CONSTANT_Utf8_info,4個位元組,Code
    #13、01 000F 4C 69 6E 65 4E 75 6D 62          //CONSTANT_Utf8_info,15個位元組,LineNumberTable
                 65 72 54 61 62 6C 65    
    #14、01 0012 4C 6F 63 61 6C 56 61 72          //CONSTANT_Utf8_info,18個位元組,LocalVariableTable
                 69 61 62 6C 65 54 61 62
                 6C 65
    #15、01 0004 74 68 69 73                       //CONSTANT_Utf8_info,4個位元組,this
    #16、01 0008 4C 50 65 72 73 6F 6E 3B           //CONSTANT_Utf8_info,8個位元組,LPerson;
    #17、01 0006 67 65 74 41 67 65                 //CONSTANT_Utf8_info,6個位元組,getAge
    #18、01 0003 28 29 49                          //CONSTANT_Utf8_info,3個位元組,()I
    #19、01 0004 77 6F 72 6B                       //CONSTANT_Utf8_info,4個位元組,work
    #20、01 0004 6D 61 69 6E                       //CONSTANT_Utf8_info,4個位元組,main
    #21、01 0016 28 5B 4C 6A 61 76 61 2F           //CONSTANT_Utf8_info,22個位元組,([Ljava/lang/String;)V
                 6C 61 6E 67 2F 53 74 72
                 69 6E 67 3B 29 56
    #22、01 0004 61 72 67 73                       //CONSTANT_Utf8_info,4個位元組,args
    #23、01 0013 5B 4C 6A 61 76 61 2F 6C           //CONSTANT_Utf8_info,19個位元組,[Ljava/lang/String;
                 61 6E 67 2F 53 74 72 69
                 6E 67 3B
    #24、01 000A 53 6F 75 72 63 65 46 69           //CONSTANT_Utf8_info,10個位元組, SourceFile
                 6C 65
    #25、01 000B 50 65 72 73 6F 6E 2E 6A           //CONSTANT_Utf8_info,11個位元組, Person.java
                 61 76 61
    #26、0C 000A 000B                              //CONSTANT_NameAndType_info,#10,#11
    #27、0C 0008 0009                              //CONSTANT_NameAndType_info,#8,#9
    #28、07 0023                                   //CONSTANT_Class_info,#35
    #29、0C 0024 0025                              //CONSTANT_NameAndType_info,#36,#37
    #30、01 0006 E5 B7 A5 E4 BD 9C                 //CONSTANT_Utf8_info,6個位元組,工作
    #31、07 0026                                   //CONSTANT_Class_info,#38
    #32、0C 0027 0028                              //CONSTANT_NameAndType_info,#39,#40
    #33、01 0006 50 65 72 73 6F 6E                 //CONSTANT_Utf8_info,6個位元組,Person
    #34、01 0010 6A 61 76 61 2F 6C 61 6E           //CONSTANT_Utf8_info,16個位元組,java/lang/Object
                 67 2F 4F 62 6A 65 63 74
    #35、01 0010 6A 61 76 61 2F 6C 61 6E           //CONSTANT_Utf8_info,16個位元組, java/lang/System
                 67 2F 53 79 73 74 65 6D
    #36、01 0003 6F 75 74                          //CONSTANT_Utf8_info,3個位元組,out
    #37、01 0015 4C 6A 61 76 61 2F 69 6F           //CONSTANT_Utf8_info,21個位元組, Ljava/io/PrintStream;
                 2F 50 72 69 6E 74 53 74
                 72 65 61 6D 3B
    #38、01 0013 6A 61 76 2F 69 6F 2F 50           //CONSTANT_Utf8_info,19個位元組,java/io/PrintStream
                 72 69 6E 74 53 74 72 65
                 61 6D
    #39、01 0007 70 72 69 6E 74 6C 6E              //CONSTANT_Utf8_info,7個位元組,println
    #40、01 0015 28 4C 6A 61 76 61 2F 6C           //CONSTANT_Utf8_info,21個位元組, (Ljava/lang/String;)V
                 61 6E 67 2F 53 74 72 69
                 6E 67 3B 29 56
    複製程式碼

    也可以java -verbose分析Class檔案位元組碼,得到結果:

    
    Constant pool:
       #1 = Methodref          #7.#26         // java/lang/Object."":()V
       #2 = Fieldref           #6.#27         // Person.age:I
       #3 = Fieldref           #28.#29        // java/lang/System.out:Ljava/io/PrintStream;
       #4 = String             #30            // 工作
       #5 = Methodref          #31.#32        // java/io/PrintStream.println:(Ljava/lang/String;)V
       #6 = Class              #33            // Person
       #7 = Class              #34            // java/lang/Object
       #8 = Utf8               age
       #9 = Utf8               I
      #10 = Utf8               
      #11 = Utf8               ()V
      #12 = Utf8               Code
      #13 = Utf8               LineNumberTable
      #14 = Utf8               LocalVariableTable
      #15 = Utf8               this
      #16 = Utf8               LPerson;
      #17 = Utf8               getAge
      #18 = Utf8               ()I
      #19 = Utf8               work
      #20 = Utf8               main
      #21 = Utf8               ([Ljava/lang/String;)V
      #22 = Utf8               args
      #23 = Utf8               [Ljava/lang/String;
      #24 = Utf8               SourceFile
      #25 = Utf8               Person.java
      #26 = NameAndType        #10:#11        // "":()V
      #27 = NameAndType        #8:#9          // age:I
      #28 = Class              #35            // java/lang/System
      #29 = NameAndType        #36:#37        // out:Ljava/io/PrintStream;
      #30 = Utf8               工作
      #31 = Class              #38            // java/io/PrintStream
      #32 = NameAndType        #39:#40        // println:(Ljava/lang/String;)V
      #33 = Utf8               Person
      #34 = Utf8               java/lang/Object
      #35 = Utf8               java/lang/System
      #36 = Utf8               out
      #37 = Utf8               Ljava/io/PrintStream;
      #38 = Utf8               java/io/PrintStream
      #39 = Utf8               println
      #40 = Utf8               (Ljava/lang/String;)V
    複製程式碼

  • 訪問標誌
  • 在常量池之後緊接著兩個位元組代表訪問標誌,用於識別一些類或者介面層次的訪問資訊
    具體的標誌位和含義如下:

    名稱 標誌值 含義
    ACC_PUBLIC 0×0001 是否為public
    ACC_FINAL 0x0010 是否為final
    ACC_SUPER 0x0020 JDK 1.0.2之後編譯出來的類這個標誌都為真
    ACC_INTERFACE 0x0200 是否為一個介面
    ACC_ABSTRACT 0x0400 是否為abstract型別
    ACC_SUPER 0x0020 JDK 1.0.2之後編譯出來的類這個標誌都為真
    ACC_SYNTHETIC 0x1000 標識這個類並非由使用者程式碼產生
    ACC_ANNOTATION 0x2000 是否是註解
    ACC_ENUM 0x4000 是否是列舉

    沒有使用到的標誌位要求一律為0,本例access_flags的值為:ACC_PUBLIC | ACC_SUPER = 0x0021

  • 類索引、父類索引與介面索引
  • 類索引、父類索引與介面索引(指向常量池)都是u2型別的資料,除了java.lang.Object
    之外所有的Java類都有父類,沒有實現介面計數器為0,本例:

     
        0006         //this_class     Person
        0007         //super_class    java/lang/Object
        0000         //沒有實現結構故0
    複製程式碼

  • 欄位表集合
  • 欄位表用於描述類和介面中宣告的變數。欄位包括類級變數和例項級變數,但是不包括方法中的變數。欄位資訊:欄位的作用域,public/private/protected 例項變數還是類變數,static 可變性,final 併發可見性, volatile 可否被序列化, transient,欄位資料型別(基本型別,物件,陣列),欄位名稱

    欄位表結構:

    型別 名稱 數量
    u2 access_flags 1
    u2 name_index 1
    u2 descriptor_index 1
    u2 attributes_count 1
    attribute_info attributes attributes_count

    對於本例:

    
        0001             //fields_count 欄位容量即1個欄位
        0002             //訪問標誌 private
        0008             //常量池第8項,即age
        0009             //欄位描述符,常量池第9項,即I
        0000             //attribute_count
    複製程式碼

  • 方法表集合
  • Class檔案儲存格式中對方法的描述與對欄位的描述幾乎採用完全一致的方式。
    方法表結構:

    型別 名稱 數量
    u2 access_flags 1
    u2 name_index 1
    u2 descriptor_index 1
    u2 attributes_count 1
    attribute_info attributes attributes_count

    對於本例:

    
        0004         //方法容量,即4個方法:例項構造器、getAge()、work以及main方法
        0001         //方法訪問標誌,public
        000A         //常量池第10項,
        000B         //方法描述常量池第11個,()V,沒返回值
        0001         //attribute_count
        000C         //常量池第12項,Code屬性表,存放方法裡的Java程式碼
        0000002F     //屬性表長度  47
        ... 47個位元組後
    
    0001         //方法訪問標誌,public
    0011         //常量池第17項,getAge
    0012         //方法描述常量池第18個,()I  返回int型
    0001         //attribute_count
    000C         //常量池第12項,Code屬性表,存放方法裡的Java程式碼
    0000002F     //屬性表長度  47
    ... 47個位元組後
    
    0029         // ACC_PUBLIC,ACC_STATIC,ACC_SYNCHRONIZED  0×0001|0×0008|0×0020
    0013         //常量池第19項,work
    000B         //方法描述常量池第11個,()V,沒返回值
    0001         //attribute_count
    000C         //常量池第12項,Code屬性表,存放方法裡的Java程式碼
    00000025     //屬性表長度  37
    ...37個位元組後
    
    0009         //ACC_PUBLIC, ACC_STATIC  0×0001|0×0008
    0014         //常量池第20項,main
    0015         //方法描述常量池第21項,([Ljava/lang/String;)V  String陣列形參,無返回型別方法
    0001         //attribute_count
    000C         //常量池第12項,Code屬性表,存放方法裡的Java程式碼
    0000002B     //屬性表長度  43
    複製程式碼

    複製程式碼

    複製程式碼

  • 屬性表集合
  • 對於本例:

    構造方法:

    
        000C                     //常量池第12項,Code屬性
        0000002F                 //Code屬性表長度  47
        0001                     //max_stack 運算元棧深度最大值  1
        0001                     //max_locals   區域性變數儲存
        00000005                 //code_length  位元組碼長度
        2A B7 00 01 B1           //位元組碼指令
        0000                     //exception_table_length
        0002                     //attributes_count  2個屬性
        
        000D                     //常量池第13項, LineNumberTable屬性
        00000006                 //LineNumberTable屬性表長度
        0001                     //line_number_table_length
        0000                     //start_pc  位元組碼行號
        0001                     //line_number  Java原始碼行號
        
        000E                     //常量池第14項,LocalVariableTable屬性
        0000000C                 //attribute_length 
        0001                     //local_variable_table_length
        0000                     //start_pc 這個區域性變數的生命週期開始的位元組碼偏移量
        0005                     //區域性變數作用範圍覆蓋的長度
        000F                     //name_index 區域性變數名稱 常量池第15項,this
        0010                     //descriptor_index 區域性變數描述 常量池第16項,LPerson;
        0000                     //這個區域性變數在棧幀區域性變數表中Slot的位置
    複製程式碼

    getAge方法

    
        000C                     //常量池第12項,Code屬性
        0000002F                 //attribute_length 
        0001                     //max_stack 運算元棧深度最大值  1
        0001                     //max_locals   區域性變數儲存
        00000005                 //code_length  位元組碼長度
        2A B4 00 02 AC           //位元組碼指令
        0000                     //exception_table_length
        0002                     //attributes_count  2個屬性
        
        000D                     //常量池第13項, LineNumberTable屬性
        00000006                 //LineNumberTable屬性表長度
        0001                     //line_number_table_length
        0000                     //start_pc  位元組碼行號
        0006                     //line_number  Java原始碼行號
        
        000E                     //常量池第14項,LocalVariableTable屬性
        0000000C                 //attribute_length
        0001                     //local_variable_table_length
        0000                     //start_pc 這個區域性變數的生命週期開始的位元組碼偏移量
        0005                     //區域性變數作用範圍覆蓋的長度
        000F                     //name_index 區域性變數名稱 常量池第15項,this
        0010                     //descriptor_index 區域性變數描述 常量池第16項,LPerson;
        0000                     //這個區域性變數在棧幀區域性變數表中Slot的位置
    複製程式碼

    work方法

    
        000C                         //常量池第12項,Code屬性
        00000025                     //attribute_length
        0002                         //max_stack 運算元棧深度最大值  2
        0000                         //max_locals   區域性變數儲存
        00000009                     //code_length  位元組碼長度
        B2 00 03 12 04 B6 00 05 B1   //位元組碼指令
        0000                         //exception_table_length
        0001                         //attributes_count  1個屬性
        
        000D                         //常量池第13項, LineNumberTable屬性
        0000000A                     //LineNumberTable屬性表長度
        0002                         //line_number_table_length  2個line_number_info
        0000                         //start_pc  位元組碼行號
        000A                         //line_number  Java原始碼行號
        0008                         //start_pc  位元組碼行號
        000B                         //line_number  Java原始碼行號
    複製程式碼

    main方法

    
        000C                         //常量池第12項,Code屬性
        0000002B                     //attribute_length
        0000                         //max_stack 運算元棧深度最大值  0
        0001                         //max_locals   區域性變數儲存
        00000001                     //code_length  位元組碼長度
        B1                           //位元組碼指令
        0000                         //exception_table_length
        0002                         //attributes_count  2個屬性
        
        000D                         //常量池第13項, LineNumberTable屬性
        00000006                     //LineNumberTable屬性表長度
        0001                         //line_number_table_length  1個line_number_info
        0000                         //start_pc  位元組碼行號
        000F                         //line_number  Java原始碼行號
        
        000E                         //常量池第14項,LocalVariableTable屬性
        0000000C                     //attribute_length
        0001                         //local_variable_table_length
        0000                         //start_pc 這個區域性變數的生命週期開始的位元組碼偏移量
        0001                         //區域性變數作用範圍覆蓋的長度
        0016                         //name_index 區域性變數名稱 常量池第22項,args
        0017                         //descriptor_index 區域性變數描述 常量池第23項,[Ljava/lang/String;
        0000                         //這個區域性變數在棧幀區域性變數表中Slot的位置
    複製程式碼

    總結

    本篇做了一個小小的嘗試,按照資料項表格一一解析,感興趣的同學可以讀下《深入理解Java虛擬機器》這本聖書。

    感謝

    《深入理解Java虛擬機器》

    相關文章