jvm原理(25)Java字節碼文件結構剖析

編寫java文件:

public class MyTest1 {
    private int a = 1;

    public int getA() {
        return a;
    }

    public void setA(int a) {
        this.a = a;
    }
}

我要要看一下java文件對應的class文件的結構,定位到工程的out\production\classes下邊執行:

javap -c com.twodragonlake.jvm.bytecode.MyTest1

得到字節碼文件結構:

Compiled from "MyTest1.java"
public class com.twodragonlake.jvm.bytecode.MyTest1 {
  public com.twodragonlake.jvm.bytecode.MyTest1();
    Code:
       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

  public int getA();
    Code:
       0: aload_0
       1: getfield      #2                  // Field a:I
       4: ireturn

  public void setA(int);
    Code:
       0: aload_0
       1: iload_1
       2: putfield      #2                  // Field a:I
       5: return
}

也可以使用 javap -verbose com/twodragonlake/jvm/bytecode/MyTest1
打印更多信息:

Classfile /E:/Study/intelIde/jvm_lecture/out/production/classes/com/twodragonlake/jvm/bytecode/MyTest1.class
  Last modified 2018-7-28; size 507 bytes
  MD5 checksum 372d96fd3e5e97ce3e964e7f9e1e2d67
  Compiled from "MyTest1.java"
public class com.twodragonlake.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         // com/twodragonlake/jvm/bytecode/MyTest1.a:I
   #3 = Class              #22            // com/twodragonlake/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               Lcom/twodragonlake/jvm/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               com/twodragonlake/jvm/bytecode/MyTest1
  #23 = Utf8               java/lang/Object
{
  public com.twodragonlake.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 26: 0
        line 27: 4
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      10     0  this   Lcom/twodragonlake/jvm/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 30: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/twodragonlake/jvm/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 34: 0
        line 35: 5
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       6     0  this   Lcom/twodragonlake/jvm/bytecode/MyTest1;
            0       6     1     a   I
}

ok,我們也可以使用二進制文件查看器查看class文件的16進制信息(winhex下載):
這裏寫圖片描述
16文件查看器裏邊第一行的CA 就是一個字節的容量(8位bit)。
1、使用javap -verbos 命令分析一個字節碼文件時,將會分析該字節碼文件的魔數、版本號、常量池、類信息、類的構造方法信息、類變量與成員變量等信息。
2、魔數:所有的.class字節碼文件的前4個字節都是魔數,魔數值爲固定值:0xCAFEBABE (詹姆斯.高斯林設計的,蘊意:咖啡寶貝,java的圖標是咖啡,哈哈哈,因缺思廳~~~很有意思的一件事情)。
3、魔數之後的4個字節爲版本信息,前2個字節表示minor versio(次版本號),後兩個字節表示major version(主版本號)。
接下來分析剩下的16進制信息的含義。
00 00 00 34 :版本信息,00 00 (前2個字節)代表是次版本號(minor version),00 34 (後2個字節)代表的是主版本號major version,34是16進制,轉換爲10進制:3*16+4=52,52是jdk8,51是jdk7,50是jdk6,以此類推,可以通過java -version命令來驗證這一點。

$ java -version
java version "1.8.0_111"
Java(TM) SE Runtime Environment (build 1.8.0_111-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.111-b14, mixed mode)

java version “1.8.0_111” 中1.8是主版本號,0是次版本號,111是更新版本號。
這個和javap反編譯出來的major version是52是對應的。1.7的jdk不能運行1.8編譯出來的class文件。低版本不可以兼容高版本的class文件。
4、常量池(constant pool):緊接着版本號之後的就是常量池入口,一個java類中定義的很多信息就是由常量池來維護和描述的,可以將常量池看作是Class文件的資源倉庫,比如說java類中定義的方法與變量信息,都是存儲在常量池中。常量池中主要存儲兩類常量:字面量與符號引用。字面量比如文本字符串,java中聲明爲final的常量等,而符號引用如類和接口的全侷限定名,字段的名稱和描述符,方法的名稱和描述符等。
5、常量池的總體結構:java類所對應的常量池主要由常量池數量與常量池數組這2部分共同構成。常量池數量緊跟在主版本號後面,佔據2個字節;常量池數組則緊跟在常量池數量之後。常量池數組與一般的數組不同的是,常量池數組中不同的元素的類型、結構都是不同的;但是每一種元素的第一個數據都是一個u1類型,該字節是個標誌位,佔據1個字節。jvm在解析常量池時,會根據這個ul類型來獲取元素的具體類型。
ok那麼主版本號之後的2個字節是常量池數量,即 00 34 後邊的 00 18 是常量池數量18對應的十進制是1*16+8=24,所以常量池的數量書24個。
但是我們用javap反編譯出來的Constant pool是23個,那是因爲常量池中元素的個數=常量池數 - 1 (其中0暫時不使用),目的是滿足某些常量池索引值的數據在特定情況下需要表達【不引用任何一個常量池】的含義;根本原因在於,索引爲0也是一個常量(保留常量),只不過他不位於常量表中,這個常量就對應null值;所以,常量池的索引從1而非0開始。
那麼我看到的16進制文件中 00 18 後邊的就是實際的一個個的元素體,每個常量元素的大小都是不一樣的,那麼每個元素的結構是怎樣的呢?
這裏寫圖片描述
00 18 後邊是第一個常量,第一個字節是標誌位:0A(十進制10),十進制10在java字節碼錶格中對應的是CONSTANT_Methodref_info常量,那麼後邊的2個字節00 04 (十進制4)就是U2(第一個index),即指向聲明方法的類描述符CONSTANT_Class_info的索引項,而第二個索引(第二個index)00 14(十進制20) 指向名稱及類型描述符CONSTANT_NameAndType_info的索引項,到此0A 00 04 00 14表示了第一個常量。那麼我們藉助反編譯工具看一下4和20的索引位置時什麼:

   #1 = Methodref          #4.#20         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#21         // com/twodragonlake/jvm/bytecode/MyTest1.a:I
   #3 = Class              #22            // com/twodragonlake/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               Lcom/twodragonlake/jvm/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               com/twodragonlake/jvm/bytecode/MyTest1
  #23 = Utf8               java/lang/Object

4是【 #4 = Class #23 // java/lang/Object】即指向聲明方法的類描述符CONSTANT_Class_info的索引項;
4引用的是23,23對應的是 java/lang/Object,
20是【#20 = NameAndType #7:#8 // “<init>“:()V】 指向名稱及類型描述符CONSTANT_NameAndType_info的索引項;
20指向的是索引7和8,7是 <init>,8是()V
這個和表格裏邊是一致的。
tip:
6、在JVM規範中,每個變量/字段都有描述信息,描述信息主要的作用是描述字段的數據類型,方法的參數列表(包括數量、類型與順序)與返回值。根據描述符規則,基本數據類型和代表無返回值的void類型都用一個大寫字符來表示,對象類型則使用字符L加對象的全限名稱來表示。爲了壓縮字節碼文件的體積,對於基本數據類型,JVM基本數據類型,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 getRealNameByIdAndNickName(int id,String name)的描述符爲:(I,Ljava/lang/String;)Ljava/lang/String;
接下來是第二個常量:09 00 03 00 15 ,09是標誌位對用的是CONSTANT_Fieldref_info,第一個索引指向的是聲明字段的類或接口描述符,CONSTANT_Class_info的索引項。
第二個索引:指向字段描述符CONSTANT_NameAndType_info 的索引項。
00 03十進制是索引3 ,3對應的是【#22 // com/twodragonlake/jvm/bytecode/MyTest1】,而22對應的是
【#22 = Utf8 com/twodragonlake/jvm/bytecode/MyTest1】
00 15十進制是21,21對應的是【 #21 = NameAndType #5:#6 // a:I】,索引5對應的是【 #5 = Utf8 a】
所用6對應的【#6 = Utf8 I】所以第二個常量表示爲:【 #3.#21 // com/twodragonlake/jvm/bytecode/MyTest1.a:I】
接下來是第三個常量:07 00 16:
00 16 十進制是22 ,07是常量CONSTANT_CLass_info,只有一個index,指向的是指定權限定名常量項的索引, 00 16 是十進制22,22是【 #22 = Utf8 com/twodragonlake/jvm/bytecode/MyTest1】
接下來是第四個常量:07 00 17 ,07是常量CONSTANT_CLass_info,只有一個index,指向的是指定權限定名常量項的索引,00 17 十進制是23,
23指向的是【#23 = Utf8 java/lang/Object】
接下來是第五個常量:01 00 01 … (後邊的字節大小受00 01的約束) ,01 是常量CONSTANT_UTF8_Info,U2是length表示UTF-8編碼的字符串的長度(佔2個字節),
然後能是U1,表示bytes,長度是length的UTF-8編碼的字符串長度。
所以00 01 是佔用2個字節的表示bytes的長度, 00 01 的十進制是1,即後邊的一個字節是bytes,後邊的一個字節是61,61在asc碼錶裏邊是a的索引,即
01 00 01 61 表示字母a。就是反編譯出來的第五個常量:【#5 = Utf8 a】。
第六個常量:01 00 01 ,01 是常量CONSTANT_UTF8_Info長度:00 01(十進制1),後邊是1個字節是:49,49對應的十進制4*16+9=75,75對應的是字母I,
所以第六個常量:【#6 = Utf8 I】
第七個常量:01 00 06 ,還是常量CONSTANT_UTF8_Info,長度00 06 是6,往後6個字節:
這裏寫圖片描述
對應的是<init>,即, 【#7 = Utf8 <init>
第八個常量01 00 03 還是是常量CONSTANT_UTF8_Info,長度00 03 是長度3,往後三個字節是:28 29 56 ,對應的是:() V,即常量:
【#8 = Utf8 ()V】
第九個常量:01 00 04 是是常量CONSTANT_UTF8_Info,長度00 04是4個字節,往後4個字節是:43 6F 64 65 表示:Code,即常量:
【#9 = Utf8 Code】
第十個常量:01 00 0F:是常量CONSTANT_UTF8_Info,長度是00 0F(十進制15),往後15個字節:4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65
表示LineNumberTable,即常量:【#10 = Utf8 LineNumberTable】,
第十一個常量:01 00 12 是常量CONSTANT_UTF8_Info ,長度00 12 (十進制18),往後18個字節是:4C6F63616C5661726961626C655461626C65
對應:LocalVariableTable,即常量:【#11 = Utf8 LocalVariableTable】
第十二個常量:01 00 04 是常量CONSTANT_UTF8_Info,長度00 04 (十進制4),往後4個字節:74686973 表示字符串“this”,即常量:【#12 = Utf8 this】
第十三個常量:01 00 28 是常量CONSTANT_UTF8_Info,長度00 28(十進制40),往後40個字節:4C636F6D2F74776F647261676F6E6C616B652F6A766D2F62797465636F64652F4D7954657374313B 表示字符串Lcom/twodragonlake/jvm/bytecode/MyTest1;
即常量:#13 = Utf8 Lcom/twodragonlake/jvm/bytecode/MyTest1;
第十四個變量:01 00 04 常量CONSTANT_UTF8_Info,長度00 04 (十進制4),往後4個字節:67657441 表示字符串”getA”。即,常量 【#14 = Utf8 getA】
第十五個變量:01 00 03 是常量CONSTANT_UTF8_Info,長度00 03 (十進制3)往後三個字節:28 29 49 表示字符串”()I”,即,常量:【#15 = Utf8 ()I】
14號和15號常量表示了一個方法,表示一個方法:方法的名字和方法的描述法,14號常量時方法的名字,15號常量時方法的描述符(沒有參數,但是有一個int的返回值)。
第十六個常量: 01 00 04 常量CONSTANT_UTF8_Info,長度00 04 (十進制4),往後4個字節:73 65 74 41 表示字符串:”setA”,即常量:【#16 = Utf8 setA】
第十七個常量:01 00 04 常量CONSTANT_UTF8_Info,長度00 04 (十進制4),往後4個字節:28 49 29 56 表示字符串”(I)V”,即常量:【#17 = Utf8 (I)V】
16和17號常量表示了一個方法:方法名字setA,方法有一個int類型的入參,但是沒有返回值。
第十八個常量:01 00 0A 常量CONSTANT_UTF8_Info,長度00 0A (十進制10),往後10個字節:536F7572636546696C65 表示字符串:”SourceFile”,即,常量:【#18 = Utf8 SourceFile】
第十九個常量:01 00 0C 常量CONSTANT_UTF8_Info,長度00 0C(十進制12),往後12個字節:4D7954657374312E6A617661 表示字符串:”MyTest1.java”,即常量【#19 = Utf8 MyTest1.java】
18號和19號常量表示當前的class文件是由那個源文件編譯出來的。
第20個常量:0c 00 07 常量 CONSTANT_NameAndType_info,此常量擁有2個index,第一個index佔2個字節:指向該字段或方法名稱常量項的索引;第二個index佔2個字節:指向該字段或方法描述符常量項的索引,00 07(十進制7,指向7號常量:【#7 = Utf8 <init>】 ,第二個index:00 08 (十進制8),指向的是8號常量:【#8 = Utf8 ()V】,因此,20號常量:【#20 = NameAndType #7:#8 // <init>:()V】
20號常量表示的是無參的構造方法。
第21號常量:0C 00 05 00 06 常量 CONSTANT_NameAndType_info,第一個index:00 05(十進制5)指向5號常量:【#5 = Utf8 a】,第二個索引00 06(十進制6)指向的的是6號常量:【#6 = Utf8 I】,因此。21號常量:【#21 = NameAndType #5:#6 // a:I】
第22個常量:01 00 26 常量CONSTANT_UTF8_Info,長度00 26 (十進制38),往後38個字節:636F6D2F74776F647261676F6E6C616B652F6A766D2F62797465636F64652F4D795465737431 表示字符串:”com/twodragonlake/jvm/bytecode/MyTest1”,即,常量:【#22 = Utf8 com/twodragonlake/jvm/bytecode/MyTest1】
第23個常量:01 00 10 常量CONSTANT_UTF8_Info,長度00 10 (十進制10),往後16個字節:6A6176612F6C616E672F4F626A656374 表示字符串:”java/lang/Object”,即常量:【#23 = Utf8 java/lang/Object】表示的是MyTest1的父類全量限定名。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章