JVM學習筆記——Class類文件解讀

簡述

Java源代碼經過編譯生成.class文件字節碼後再被JVM解釋轉化爲目標機器代碼,從而實現一次編寫處處,處處運行("Write Once,Run Anywhere")。字節碼與平臺無關,並且並非只有Java語言編譯爲字節碼文件在虛擬機上運行。java

類文件的結構

Class文件是一組以8位字節爲基礎單位的二進制流,各個數據項目嚴格按照順序緊湊地排列在Class文件中。Class文件只有兩種數據類型:無符號數和表。
數組

無符號數屬於基本的數據類型,有u1, u2, u4, u8,分別表明1個字節、2個字節、4個字節和8個字節的無符號數
併發

整個Class文件就是一張表,由如下數據項構成:
this

類型 名稱 數量
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文件spa

根據上述數據項表格咱們按順序拆分code

  • 魔數
  • 魔數站每一個Class文件的頭4個字節,其做用未肯定肯定這個文件是否爲一個能被虛擬機接受的Class文件 示例中CA FE BA BE爲魔數

    cdn

  • 版本號
  • 魔數後面緊跟着版本號
    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種常量含義:blog

    類型 標誌 描述
    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(常量池容量)
     #一、0A 0007 001A                             //CONSTANT_Methodref_info,#7,#26
     #二、09 0006 001B                             //CONSTANT_Fieldref_info,#6,#27
     #三、09 001C 001D                             //CONSTANT_Fieldref_info,#28,#29
     #四、08 001E                                  //CONSTANT_String_info,#30
     #五、0A 001F 0020                             //CONSTANT_Methodref_info,#31,#32
     #六、07 0021                                  //CONSTANT_Class_info,#33
     #七、07 0022                                  //CONSTANT_Class_info,#34
     #八、01 0003 61 67 65                         //CONSTANT_Utf8_info,3個字節,age
     #九、01 0001 49                               //CONSTANT_Utf8_info,1個字節,I
    #十、01 0006 3C 69 6E 69 74 3E                //CONSTANT_Utf8_info,6個字節,
    #十一、01 0003 28 29 56                         //CONSTANT_Utf8_info,3個字節,()V
    #十二、01 0004 43 6F 64 65                      //CONSTANT_Utf8_info,4個字節,Code
    #1三、01 000F 4C 69 6E 65 4E 75 6D 62          //CONSTANT_Utf8_info,15個字節,LineNumberTable
                 65 72 54 61 62 6C 65    
    #1四、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
    #1五、01 0004 74 68 69 73                       //CONSTANT_Utf8_info,4個字節,this
    #1六、01 0008 4C 50 65 72 73 6F 6E 3B           //CONSTANT_Utf8_info,8個字節,LPerson;
    #1七、01 0006 67 65 74 41 67 65                 //CONSTANT_Utf8_info,6個字節,getAge
    #1八、01 0003 28 29 49                          //CONSTANT_Utf8_info,3個字節,()I
    #1九、01 0004 77 6F 72 6B                       //CONSTANT_Utf8_info,4個字節,work
    #20、01 0004 6D 61 69 6E                       //CONSTANT_Utf8_info,4個字節,main
    #2一、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
    #2二、01 0004 61 72 67 73                       //CONSTANT_Utf8_info,4個字節,args
    #2三、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
    #2四、01 000A 53 6F 75 72 63 65 46 69           //CONSTANT_Utf8_info,10個字節, SourceFile
                 6C 65
    #2五、01 000B 50 65 72 73 6F 6E 2E 6A           //CONSTANT_Utf8_info,11個字節, Person.java
                 61 76 61
    #2六、0C 000A 000B                              //CONSTANT_NameAndType_info,#10,#11
    #2七、0C 0008 0009                              //CONSTANT_NameAndType_info,#8,#9
    #2八、07 0023                                   //CONSTANT_Class_info,#35
    #2九、0C 0024 0025                              //CONSTANT_NameAndType_info,#36,#37
    #30、01 0006 E5 B7 A5 E4 BD 9C                 //CONSTANT_Utf8_info,6個字節,工做
    #3一、07 0026                                   //CONSTANT_Class_info,#38
    #3二、0C 0027 0028                              //CONSTANT_NameAndType_info,#39,#40
    #3三、01 0006 50 65 72 73 6F 6E                 //CONSTANT_Utf8_info,6個字節,Person
    #3四、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
    #3五、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
    #3六、01 0003 6F 75 74                          //CONSTANT_Utf8_info,3個字節,out
    #3七、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
    #3八、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
    #3九、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
    複製代碼
    複製代碼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虛擬機》

    相關文章
    相關標籤/搜索