Class文件格式

 咱們知道Java是一門跨平臺的語言,咱們編寫的Java代碼會被編譯成中間class文件以讓Java虛擬機解析運行。而Java虛擬機規範僅僅描述了抽象的Java虛擬機,在實現具體的Java虛擬機時,僅指出了設計規範。Java虛擬機的實現必須體現規範中的內容,但僅在確有必要時才應該受制於這些規範。對於完整內容,能夠查看原文檔,以JDK7爲例,可查看https://docs.oracle.com/javase/specs/jvms/se7/html/,或者《深刻理解Java虛擬機 JVM高級特性與最佳實踐》一書。完整的規範主要包含如下內容:html

  • 第2章:概覽Java虛擬機總體架構
  • 第3章:介紹如何將Java語言編寫的程序轉換爲虛擬機指令集
  • 第4章:定義class文件格式。它是一種與硬件和操做系統無關的二進制格式,用來表示編譯後的類和接口
  • 第5章:定義了Java虛擬機啓動以及類和接口的加載、連接和初始化的過程
  • 第6章:定義了Java虛擬機指令集
  • 第7章:提供了一張以操做碼值爲索引的Java虛擬機操做碼助記表

 本文只是大概記錄項目須要瞭解的基礎概念,着重在介紹Class文件格式上,爲該系列後續內容作鋪墊。java

 Class文件是一組以8字節爲基礎單位的二進制流,各個數據項目嚴格按照順序緊湊排列在class文件中,中間沒有任何分割符。每一個 Class 文件都是由 8 字節爲單位的字節流組成,全部的 16 位、32 位和 64 位長度的數據將被構形成 2 個、4 個和 8 個 8 字節單位來表示。程序員

 每個Class文件對應於一個以下所示的ClassFile結構體:數組

file

涉及到的內容包括:微信

  • magic:魔數,魔數的惟一做用是肯定這個文件是否爲一個能被虛擬機所接受的Class文件。魔數值固定爲0xCAFEBABE,不會改變。
  • minor_version、major_version:副版本號和主版本號,minor_version和major_version的值分別表示Class文件的副、主版本。一個Java虛擬機實例只能支持特定範圍內的主版本號(Mi至Mj)和0至特定範圍內(0至m)的副版本號。
  • constant_pool_count:常量池計數器,constant_pool_count的值等於constant_pool表中的成員數加1。
  • constant_pool[]:常量池,constant_pool是一種表結構,它包含Class文件結構及其子結構中引用的全部字符串常量、類或接口名、字段名和其它常量。
  • access_flags:訪問標誌,access_flags是一種掩碼標誌,用於表示某個類或者接口的訪問權限及基礎屬性。
  • this_class:類索引
  • super_class:父類索引
  • interfaces_count:接口計數器,interfaces_count的值表示當前類或接口的直接父接口數量
  • interfaces[]:接口表,在interfaces[]數組中,成員所表示的接口順序和對應的源代碼中給定的接口順序(從左至右)同樣,即interfaces[0]對應的是源代碼中最左邊的接口。
  • fields_count:字段計數器,fields_count的值表示當前Class文件fields[]數組的成員個數。
  • fields[]:字段表,fields[]數組描述當前類或接口聲明的全部字段,但不包括從父類或父接口繼承的部分。
  • methods_count:方法計數器,methods_count的值表示當前Class文件methods[]數組的成員個數。
  • methods[]:方法表,methods[]數組只描述當前類或接口中聲明的方法,不包括從父類或父接口繼承的方法。
  • attributes_count:屬性計數器,attributes_count的值表示當前Class文件attributes表的成員個數。
  • attributes[]:屬性表

 可用jdk自帶的javap命令對class文件進行反編譯,以查看內容,以下代碼:架構

public class Ex {

    public void judgeAge(int age) {
        int step = 0;
        if (age > 18) {
            step++;
            System.out.println("a litter old");
        } else {
            System.out.println("a litter cute");
            step++;
        }
        System.out.println(step);
    }

    public static void main(String[] args) {
        Ex ex = new Ex();
        ex.judgeAge(16);
    }
}

執行 javap -verbose -p Ex.class的結果爲oracle

Classfile Ex.class
  Last modified 2019-11-29; size 788 bytes
  MD5 checksum 8b5d8ebf38c4441fe7150c10da31ce1b
  Compiled from "Ex.java"
public class Ex
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #10.#31        // java/lang/Object."<init>":()V
   #2 = Fieldref           #32.#33        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #34            // a litter old
   #4 = Methodref          #35.#36        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = String             #37            // a litter cute
   #6 = Methodref          #35.#38        // java/io/PrintStream.println:(I)V
   #7 = Class              #39            // Ex
   #8 = Methodref          #7.#31         // Ex."<init>":()V
   #9 = Methodref          #7.#40         // Ex.judgeAge:(I)V
  #10 = Class              #41            // java/lang/Object
  #11 = Utf8               <init>
  #12 = Utf8               ()V
  #13 = Utf8               Code
  #14 = Utf8               LineNumberTable
  #15 = Utf8               LocalVariableTable
  #16 = Utf8               this
  #17 = Utf8               LEx;
  #18 = Utf8               judgeAge
  #19 = Utf8               (I)V
  #20 = Utf8               age
  #21 = Utf8               I
  #22 = Utf8               step
  #23 = Utf8               StackMapTable
  #24 = Utf8               main
  #25 = Utf8               ([Ljava/lang/String;)V
  #26 = Utf8               args
  #27 = Utf8               [Ljava/lang/String;
  #28 = Utf8               ex
  #29 = Utf8               SourceFile
  #30 = Utf8               Ex.java
  #31 = NameAndType        #11:#12        // "<init>":()V
  #32 = Class              #42            // java/lang/System
  #33 = NameAndType        #43:#44        // out:Ljava/io/PrintStream;
  #34 = Utf8               a litter old
  #35 = Class              #45            // java/io/PrintStream
  #36 = NameAndType        #46:#47        // println:(Ljava/lang/String;)V
  #37 = Utf8               a litter cute
  #38 = NameAndType        #46:#19        // println:(I)V
  #39 = Utf8               Ex
  #40 = NameAndType        #18:#19        // judgeAge:(I)V
  #41 = Utf8               java/lang/Object
  #42 = Utf8               java/lang/System
  #43 = Utf8               out
  #44 = Utf8               Ljava/io/PrintStream;
  #45 = Utf8               java/io/PrintStream
  #46 = Utf8               println
  #47 = Utf8               (Ljava/lang/String;)V
{
  public Ex();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   LEx;

  public void judgeAge(int);
    descriptor: (I)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=2
         0: iconst_0
         1: istore_2
         2: iload_1
         3: bipush        18
         5: if_icmple     22
         8: iinc          2, 1
        11: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        14: ldc           #3                  // String a litter old
        16: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        19: goto          33
        22: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        25: ldc           #5                  // String a litter cute
        27: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        30: iinc          2, 1
        33: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        36: iload_2
        37: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
        40: return
      LineNumberTable:
        line 4: 0
        line 5: 2
        line 6: 8
        line 7: 11
        line 9: 22
        line 10: 30
        line 12: 33
        line 13: 40
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      41     0  this   LEx;
            0      41     1   age   I
            2      39     2  step   I
      StackMapTable: number_of_entries = 2
        frame_type = 252 /* append */
          offset_delta = 22
          locals = [ int ]
        frame_type = 10 /* same */

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #7                  // class Ex
         3: dup
         4: invokespecial #8                  // Method "<init>":()V
         7: astore_1
         8: aload_1
         9: bipush        16
        11: invokevirtual #9                  // Method judgeAge:(I)V
        14: return
      LineNumberTable:
        line 16: 0
        line 17: 8
        line 18: 14
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      15     0  args   [Ljava/lang/String;
            8       7     1    ex   LEx;
}
SourceFile: "Ex.java"

下面對後續須要接觸到的幾項內容作說明。app

1. 數據項

 Class文件中有兩種數據類型,分別是無符號數和表:jvm

file

  • 無符號數:屬於基本數據類型,主要能夠用來描述數字、索引符號、數量值或者按照UTF-8編碼構成的字符串值,大小使用u一、u二、u四、u8分別表示1字節、2字節、4字節和8字節
  • 表:是由多個無符號數或者其餘表做爲數據項構成的複合數據類型,全部的表都習慣以「_info」結尾

2. 訪問和修飾符標識

file

 帶有 ACC_SYNTHETIC 標誌的部分,意味着它是由編譯器本身產生的而不是由程序員編寫的源代碼生成的。有該標誌的類、屬性、方法等不會在源碼中顯示。函數

3. 類型描述符

file

 基本類型的描述是單個字符:

  • Z表示 boolean
  • C表示 char
  • B表示 byte
  • S表示 short
  • I 表示 int
  • F 表示 float
  • J 表示 long
  • D 表示 double
  • 一個類類型的描述符是這個類的內部名,前面加上字符 L,後面跟有一個分號。例如,String 的類型描述符爲 Ljava/lang/String;。而一個數組類型的 述符是一個方括號後面跟有該數組元素類型的描述符。

4. 方法描述符

 方法描述符是一個類型描述符列表,它用一個字符串描述一個方法的參數類型和返回類型。

 方法描述符以左括號開頭,而後是每一個形參的類型描述,而後是一個右括號,接下來是返回類型的類型描述符,若是該方法返回void,則是 V,表示方法描述中不包含方法的名字或參數名,可看以下例子:

file

5. 指令

 Java虛擬機的指令由一個字節長度的、表明着某種特定操做含義的操做碼(Opcode)以及跟隨其後的零至多個表明此操做所需參數的操做數(Operands)所構成。虛擬機中許多指令並不包含操做數,只有一個操做碼。

 常見的指令以下:

  • 字段訪問指令:getfield,putfield,getstatic,pustatic
  • 比較指令:dcmpg,dcmpl,fcmpg,fcmpl,lcmp
  • 跳轉指令:ifeq,iflt,ifle,ifne,ifgt,ifge,ifnull,ifnonnull
  • 比較條件跳轉指令:if_icmpeq,if_icmpne,if_icmplt,if_icmpgt,if_icmple,if_icmpge,if_acmpeq,if_acmpne
  • 多條件分支跳轉:tableswitch和lookupswitch
  • 無條件跳轉:goto
  • 函數調用與返回指令
  • 函數調用指令:invokevirtual,invokeinterface,invokespecial,invokestatic,invokedynamic;
  • 函數返回:須要將返回值壓入調用者操做數棧,須要使用xreturn指令(x能夠是i,l,f,d,a或空)

 PS:筆者我的習慣使用Bytecode Outline進行反編譯,這款插件輸出的內容可讀性會高點,上面的內容輸出下:

// class version 52.0 (52)
// access flags 0x21
public class Ex {

  // compiled from: Ex.java

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 1 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
   L1
    LOCALVARIABLE this LEx; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x1
  public judgeAge(I)V
   L0
    LINENUMBER 4 L0
    ICONST_0
    ISTORE 2
   L1
    LINENUMBER 5 L1
    ILOAD 1
    BIPUSH 18
    IF_ICMPLE L2
   L3
    LINENUMBER 6 L3
    IINC 2 1
   L4
    LINENUMBER 7 L4
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "a litter old"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
    GOTO L5
   L2
    LINENUMBER 9 L2
   FRAME APPEND [I]
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "a litter cute"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L6
    LINENUMBER 10 L6
    IINC 2 1
   L5
    LINENUMBER 12 L5
   FRAME SAME
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ILOAD 2
    INVOKEVIRTUAL java/io/PrintStream.println (I)V
   L7
    LINENUMBER 13 L7
    RETURN
   L8
    LOCALVARIABLE this LEx; L0 L8 0
    LOCALVARIABLE age I L0 L8 1
    LOCALVARIABLE step I L1 L8 2
    MAXSTACK = 2
    MAXLOCALS = 3

  // access flags 0x9
  public static main([Ljava/lang/String;)V
   L0
    LINENUMBER 16 L0
    NEW Ex
    DUP
    INVOKESPECIAL Ex.<init> ()V
    ASTORE 1
   L1
    LINENUMBER 17 L1
    ALOAD 1
    BIPUSH 16
    INVOKEVIRTUAL Ex.judgeAge (I)V
   L2
    LINENUMBER 18 L2
    RETURN
   L3
    LOCALVARIABLE args [Ljava/lang/String; L0 L3 0
    LOCALVARIABLE ex LEx; L1 L3 1
    MAXSTACK = 2
    MAXLOCALS = 2
}

更多原創內容請搜索微信公衆號:啊駝(doubaotaizi)

相關文章
相關標籤/搜索