1. 程式人生 > >深入理解JVM-java字節碼文件結構剖析(1)

深入理解JVM-java字節碼文件結構剖析(1)

ret 是個 sta 操作數棧 face 列表 屬性 基表 異常

public class MyTest1 {

    private int a = 1;

    public int getA() {
        return a;
    }

    public void setA(int a) {
        this.a = a;
    }
}
javap -verbose MyTest1
警告: 二進制文件MyTest1包含jvm.bytecode.MyTest1
Classfile /Users/luozhiyun/Documents/work/jvm_lecture/target/classes/jvm/bytecode/MyTest1.class
  Last modified Mar 14, 2019; size 471 bytes
  MD5 checksum b2dc69fae4f63b54509ddc1a9210e9c3
  Compiled from "MyTest1.java"
public class jvm.bytecode.MyTest1
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#20         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#21         // jvm/bytecode/MyTest1.a:I
   #3 = Class              #22            // jvm/bytecode/MyTest1
   #4 = Class              #23            // java/lang/Object
   #5 = Utf8               a
   #6 = Utf8               I
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Ljvm/bytecode/MyTest1;
  #14 = Utf8               getA
  #15 = Utf8               ()I
  #16 = Utf8               setA
  #17 = Utf8               (I)V
  #18 = Utf8               SourceFile
  #19 = Utf8               MyTest1.java
  #20 = NameAndType        #7:#8          // "<init>":()V
  #21 = NameAndType        #5:#6          // a:I
  #22 = Utf8               jvm/bytecode/MyTest1
  #23 = Utf8               java/lang/Object
{
  public jvm.bytecode.MyTest1();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: iconst_1
         6: putfield      #2                  // Field a:I
         9: return
      LineNumberTable:
        line 6: 0
        line 8: 4
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      10     0  this   Ljvm/bytecode/MyTest1;

  public int getA();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field a:I
         4: ireturn
      LineNumberTable:
        line 11: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Ljvm/bytecode/MyTest1;

  public void setA(int);
    descriptor: (I)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: iload_1
         2: putfield      #2                  // Field a:I
         5: return
      LineNumberTable:
        line 15: 0
        line 16: 5
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       6     0  this   Ljvm/bytecode/MyTest1;
            0       6     1     a   I
}
SourceFile: "MyTest1.java"

對應的16進制
cafe babe
魔數:所有的.class字節碼文件的前4個字節都是魔數,魔數值為固定值:OxCAFEBABE

0000 0034
魔數之後的4個字節為版本信息,前兩個字節表示minor version(次版本號),後兩個字節表示major version(主版本號)。這裏的版本號為00 00 00 34 ,換算成十進制,表示次版本號為0,主版本號為52。所以,該文件的版本號為:1.8.0

0018
表示一共有24個常量 

0a 代表值為10 Methodref_info 這個常量 
00 04 00 14 代表4 和 20   即上面反編譯的結果 #4.#20 

09 表示Fieldref  
0003 0015  表示3 和 21 即上面反編譯的結果 #3.#21

07 表示Class 
00 16 代表 #22

07 表示Class 
00 17 代表 #23

01 表示utf8
00 01 長度為1
61 代表a

01 表示utf8
00 01 長度為1
49 代表I

0c  表示NameAndType
00 07 表示指向該字段或方法名稱常量項的索引  #7:
00 08 表示指向該字段或方法描述符常量項的索引  #8

0c 
00 05 表示指向該字段或方法名稱常量項的索引  #5
00 06 表示指向該字段或方法描述符常量項的索引  #6

Access flag
00 21 表示ACC_PUBLIC 與ACC_SUPER取的並集

This Class Name
00 03 表示的一個索引指的是常量池第三個常量

This Super Name
00 04 表示的一個索引指的是常量池第四個常量

Interfaces  
00 00 表示沒有實現接口

Fields
00 01 表示屬性的數量,表示有一個字段
00 02    access_flag 表示private
00 05 name_index 名字的索引
00 06 descriptor_index 描述符的索引
00 00 表示attributes_count為0,也就沒有attributes_info

Method
00 03 代表有三個方法,包含了自動生成的默認構造器
00 01 代表是一個public的方法
00 07 代表是name_index  <init>
00 08 代表是descriptor_index. ()V
00 01 代表是attribute_count,有一個屬性
method attribute
00 09 代表屬性名的索引attribute_name_index  code
00 00 00 38 attribute_length  56個字節長度
00 02 max_stack
00 01 max_local 局部變量最大值
00 00 00 0a code_length 方法的代碼長度 10個字節,代表這個方法所真正運行的字節碼
2a b7 00 01 2a 04 b5 00 02 b1
實際上就是對應著下面的助記符
0: aload_0
1: invokespecial #1                  // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_1
6: putfield      #2                  // Field a:I
9: return

2a -> aload_0
b7 -> invokespecial
04 -> aload_0
b5 -> putfield (為成員變量設置,通過這個變量我們可以知道,成員變量其實是在構造器中賦值的)
b1 -> return

exception
00 00

attributes
00 02 attribute_count; 表示有兩個屬性
00 0a 屬性的索引 #10  LineNumberTable
00 00 00 0a attribute_length 10個字節
00 02 00 00 00 06 00 04 00 08 
00 02 表示兩兩映射  
00 00  -> 00 06 表示偏移量為0映射到行號為6
00 04  -> 00 08 表示偏移量為4映射到行號為8

00 0b 第二個屬性的索引 #11 LocalVariableTable
00 00 00 0c 表示LocalVariableTable所占的字節長度
00 01 00 00 00 0a 00 0c 00 0d 00 00
00 01 局部變量的個數
00 00   局部變量開始的位置
00 0a 局部變量結束的位置
00 0c 局部變量索引   #12 this
00 0d 索引 #13 Ljvm/bytecode/MyTest1;
00 00 用來做校驗檢查的

第二個方法
00 01 訪問修飾符 代表public
00 0e 方法的名字索引  getA
00 0f   方法的描述符的索引 ()I
00 01 代表是attribute_count,有一個屬性
00 09 代表屬性名的索引attribute_name_index  code
00 00 00 2f 屬性的長度47
00 01 max_stack
00 01 max_local 局部變量的數量是1 
00 00 00 05 code_length 方法的代碼長度 5個字節
2a b4 00 02 ac 方法的執行體
2a -> aload_0
b4 -> getField. 
00 02 表示常量池中的第二個常量 #2
ac -> ireturn 表示返回一個整型

exception
00 00

attributes
00 02 attribute_count; 表示有兩個屬性
00 0a 索引指向第10個常量 LineNumberTable
00 00 00 06 attribute_length 6個字節
00 01 00 00 00 0b 表示只有一行, 偏移量為零對應行號8
00 0b 第二個屬性的索引 #11 LocalVariableTable
00 00 00 0c 表示LocalVariableTable所占的字節長度 12個

00 01 00 00 00 05 00 0c 00 0d 00 00
00 01 局部變量的個數
00 00   局部變量開始的位置
00 05 局部變量結束的位置
00 0c 局部變量索引的位置  #12 this
00 0d 索引 #13 Ljvm/bytecode/MyTest1;
00 00 用來做校驗檢查的

第三個方法
00 01 表示public             
00 10 表示索引#16  setA
00 11   表示索引#17  (I)V
00 01 代表是attribute_count,有一個屬性
00 09 代表屬性名的索引attribute_name_index  code
00 00 00 3e code_length 方法的代碼長度 62個字節
00 02 max_stack
00 02 max_local
00 00 00 06 code_length 方法的代碼長度 6個字節
2a 1b b5 00 02 b1
2a -> aload_0
1b -> iload_1
b5 -> putfield
00 02 表示第二常量
b1 -> return 

attributes
00 02 attribute_count; 表示有兩個屬性
00 0a 索引指向第10個常量 LineNumberTable
00 00 00 0a attribute_length 10個字節
00 02 00 00 00 0f 00 05 00 10
00 02 表示有兩個對應關系
00 00 00 0f 偏移量為0的對應的是12行
00 05 00 10 偏移量為5的對應的是16行
00 0b 第二個屬性的索引 #11 LocalVariableTable
00 00 00 16 表示LocalVariableTable所占的字節長度 22個
00 02 00 00 00 06 00 0c 00 0d 00 00 00 00 00 06 00 05 00 06 00 01
00 02 局部變量的個數
00 00   局部變量開始的位置
00 06 局部變量結束的位置
00 0c 局部變量索引的位置  #12 this
00 0d 索引 #13 Ljvm/bytecode/MyTest1;
00 00   局部變量開始的位置
00 06 局部變量結束的位置
00 05 局部變量索引的位置  #5 a
00 06 索引#6 I

Attributes
00 01 
00 12 索引 #18  sourceFile
00 00 00 02 源文件的長度
00 13 索引#19  MyTest1.java

cafe babe 0000 0034 0018 0a00 0400 1409
0003 0015 0700 1607 0017 0100 0161 0100
0149 0100 063c 696e 6974 3e01 0003 2829
5601 0004 436f 6465 0100 0f4c 696e 654e
756d 6265 7254 6162 6c65 0100 124c 6f63
616c 5661 7269 6162 6c65 5461 626c 6501
0004 7468 6973 0100 164c 6a76 6d2f 6279
7465 636f 6465 2f4d 7954 6573 7431 3b01
0004 6765 7441 0100 0328 2949 0100 0473
6574 4101 0004 2849 2956 0100 0a53 6f75
7263 6546 696c 6501 000c 4d79 5465 7374
312e 6a61 7661 0c00 0700 080c 0005 0006
0100 146a 766d 2f62 7974 6563 6f64 652f
4d79 5465 7374 3101 0010 6a61 7661 2f6c
616e 672f 4f62 6a65 6374 0021 0003 0004
0000 0001 0002 0005 0006 0000 0003 0001
0007 0008 0001 0009 0000 0038 0002 0001
0000 000a 2ab7 0001 2a04 b500 02b1 0000
0002 000a 0000 000a 0002 0000 0006 0004
0008 000b 0000 000c 0001 0000 000a 000c
000d 0000 0001 000e 000f 0001 0009 0000
002f 0001 0001 0000 0005 2ab4 0002 ac00
0000 0200 0a00 0000 0600 0100 0000 0b00
0b00 0000 0c00 0100 0000 0500 0c00 0d00
0000 0100 1000 1100 0100 0900 0000 3e00
0200 0200 0000 062a 1bb5 0002 b100 0000
0200 0a00 0000 0a00 0200 0000 0f00 0500
1000 0b00 0000 1600 0200 0000 0600 0c00
0d00 0000 0000 0600 0500 0600 0100 0100
1200 0000 0200 13
  1. 使用javap -verbose 命令分析一個字節碼文件時,將會分析該字節碼文件的魔數、版本號、 常量池、類的構造方法、類中的方法信息、類變量與成員變量等信息。

  2. 魔數:所有的.class字節碼文件的前4個字節都是魔數,魔數值為固定值:OxCAFEBABE
  3. 魔數之後的4個字節為版本信息,前兩個字節表示minor version(次版本號),後兩個字節表示major version(主版本號)。這裏的版本號為00 00 00 34 ,換算成十進制,表示次版本號為0,主版本號為52。所以,該文件的版本號為:1.8.0
  4. 常量池(content pool):緊接著主版本號之後的就是常量池入口。一個java類中定義的很多信息都是由常量池來維護和描述的。可以將常量池看坐是Class文件的資源倉庫,比如說java類中定義的方法與變量信息,都是存儲在常量池中。常量池中主要存儲兩類常量:字面量與符號引用。字面量如文本字符串,java中聲明為final的常量值等,而符號引用如類和接口的全局限定名,字段的名稱和描述符,方法的名稱和描述符等。
  5. 常量池的總體結構:java類所對應的常量池主要由常量池數量與常量池數組這兩部分共同構成。常量池數量緊跟在主版本號後面,占據2個字節;常量池數組則緊跟在常量池數量之後。常量池數組與一般的數組不同的是,常量池數組中不同的元素的類型、結構都是不同的,長度當然也就不同;但是,每一種元素的第一個數據都是一個u1類型,該字節是個標誌位,占據1個字節。jvm在解析常量池時,會根據這個u1類型來獲取元素的具體類型。值得註意的是,常量池數組中元素的個數 = 常量池數 -1 (其中0暫時不適用),目的是滿足某些常量池索引值的數據在特定情況下需要表達「不引用任何一個常量池」的含義;根本原因在於,索引為0也是一個常量(保留常量),只不過它部位與常量表中,這個常量就對應null值;所以,常量池的索引從1而非0開始。
  6. 在jvm規範中,每個變量/字段都有描述信息,描述信息主要的作用是描述字段的數據類型、方法的參數列表(包含數量、類型與順序)與返回值。根據描述符規則,基本數據類型和代表無返回值的void類型都用一個大寫字符來表示,對象類型則使用字符L加對象的全限定名稱來表示。為了壓縮字節碼文件的體積,對於基本數據類型,jvm都只使用一個大寫字母來表示,如下所示:B- byte , C - char, D - double, F -float, I - int, J -long, S - short , Z - boolean, V - void, L -對象類型,如Ljava/lang/String;
  7. 對於數組類型來說,每一個維度使用一個前置的[來表示,如int[]被記錄為[I,String[][]被記錄為[[Ljava/lang/String;
  8. 用描述符來描述方法時,按照先參數列表,後返回值的順序來描述,參數列表按照參數的嚴格順序放在一組()之內,如方法:String getRealnamByIdANdNickName(int id,String name)的描述符為:(I,Ljava/lang/String;)Ljava/lang/String;
    技術分享圖片

Java字節碼整體結構

  • 4個字節 Magic Number 魔數值為OxCAFEBABE
  • 2+2個字節 Version 前兩個字節表示minor version(次版本號),後兩個字節表示major version(主版本號)。1.1(45),1.2(46),1.3(47),1.4(48),1.5(49),1.6(50),1.7(51)
  • 2+ n個字節 Constant Pool 包括字符串常量、數值常量等
  • 2個字節 Access Flags 訪問標誌(public class 、private class 等)
  • 2個字節 This Class Name
  • 2個字節 Super Class Name
  • 2+n個字節 Interfaces
  • 2+n個字節 Fields 當前這個類的成員變量的信息
  • 2+n個字節 Methods
  • 2+n個字節 Attributes 當前這個類的附加的屬性

Class字節碼中有兩種數據類型
* 字節數據直接量:這事基本的數據類型。共細分為u1、u2、u4、u8四種,分別代表連續的1個字節、2個字節、4個字節、8個字節組成的整體數據
* 表(數組):表是由多個基本數據或其他表,按照既定順序組成的大的數據集合。表是有結構的,它的結構體現在:組成表的成分所在的位置和順序都是已經嚴格定義好的。

技術分享圖片

ClssFile{
    u4              magic;
    u2              minor_version;
    u2              major_version;
    u2              constant_pool_count;
    cp_info         contant_pool[constant_pool_count -1];
    u2              access_flags;
    u2              this.class;
    u2              super.class;
    u2              interfaces_count;
    u2              interfaces[interfaces_count];
    u2              fields_count;
    field_info      fields[fields_count];
    u2              method_count;
    method_info     methos[method_count];
    u2              attributes_count;
    attribute_info  attributes[attributes_count];
}

Access_Flag訪問標誌

訪問標誌信息包括該Class文件是類還是接口,是否被定義成public,是否是abstract,如果是類,是否被聲明成final。通過上面的源代碼,我們知道該文件是類並且是public。
技術分享圖片

字段表集合
字段表用於描述類和接口中聲明的變量。這裏的字段包含了類基表變量以及實例變量,但是不包括方法內部聲明的局部變量。
技術分享圖片

field_info{
    u2              access_flags; 0002
    u2              name_index;  0005
    u2              descriptor_index; 0006
    u2              attributes_count;   0000
    attribute_info  attributes[attributes_count];
}

方法表

技術分享圖片

前三個字段和field_info一樣

method_info{
    u2                  access_flags;
    u2                  name_index;
    u2                  descriptor_index;
    u2                  attributes_count;
    attribute_info      attributes[attributes_count];
}

方法的屬性結構
方法中的每個屬性都是一個attribute_info結構

attribute_info{
    u2  attribute_name_index;
    u4  attribute_length;
    u1  info[attribute_length];
}
* jvm預定義了部分attribute,但是編譯器自己也可以實現自己的attribute寫入class文件裏,供運行時使用。
* 不同的attribute通過attribute_name_index來區分

Code結構

Code attribute的作用是保存該方法的結構,如所對應的字節碼

Code_attribute{
    u2  attribute_name_index;
    u4  attribute_length;
    u2  max_stack;
    u2  max_locals;
    u4  code_length;
    u1  code[code_length];
    u2  exception_table_length;
    {
        u2  start_pc;
        u2  end_pc;
        u2  handler_pc;
        u2  catch_type;
    }   exception_table[exception_table_length];
    u2  attribute_count;
    attribute_info attributes[attribute_count];
}
* attribute_length表示attribute所包含的字節數,不包含attribute_name_index和attribute_length字段
* max_stack表示這個方法運行的任何時刻所能達到的操作數棧的最大深度
* max_locals表示方法執行期間創建的局部變量的數目,包含用來表示傳入的參數的局部變量
* code_length表示該方法所包含的字節碼的字節數以及具體的指令碼
* 具體字節碼即是該方法被調用時,虛擬機所執行的字節碼
* exception_table,這裏存放的是處理異常的信息
* 每個exception_table表項由start_pc, end_pc,  handler_pc, catch_type組成
* start_pc和end_pc表示在code數組中的從start_pc到end_pc處(包含start_pc,不包含end_pc)的指令拋出的異常會由這個表項來處理
* handler_pc表示處理異常的代碼的開始處。catch_type表示會被處理的異常類型,它指向常量池的一個異常類。當catch_type為0時,表示處理所有的異常

附加屬性

接下在是該方法的附加屬性
LineNumberTable:這個屬性用來表示code數組中的字節碼和java代碼行數之間的關系。這個屬性可以用來在調試的時候定位代碼執行的行數

lineNumberTable_attribute{
    u2 attribute_name_index;
    u4  attribute_length;
    u2  line_number_table_length;
    {
        u2 start_pc
        u2  line_number;
    }line_number_table[line_number_table_length];
}

上面的信息可以用jClasslib這樣的一個工具來查看

深入理解JVM-java字節碼文件結構剖析(1)