JVM學習筆記之class文件結構【七】

一、概念

1.1 無符號數:

以 u1、u2、u3、u4、u8 代表 1 個字節,2 個字節、4 個字節、8 個字節的無符號數。無符號數可以描述數字,索引引用、數量值和按照 UTF-8 編碼構成的字符串值。

1.2 表

  • 表是由多個無符號數或其他表作為數據項構成的復合的數據結構,所有表都習慣性的以“_info”結尾。表用于表示有層次關系的復合結構的數據,整個 Class 文件本質上是一張表

1.3 class 文件組成

ClassFile {
    u4             magic;  //魔數, 用于識別class文件格式
    u2             minor_version;//次版本號
    u2             major_version;//主版本號
    u2             constant_pool_count;  //常量池計數器
    cp_info        constant_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             methods_count;//方法計數器
    method_info    methods[methods_count];//方法表
    u2             attributes_count; //屬性計數器
    attribute_info attributes[attributes_count];附加屬性表
}

1.4 魔數

每個 Class 文件的頭 4 個字節被稱為魔數(Magic Number),它的唯一作用是確定這個文件是否能被虛擬機接受的 Class 文件。它的值是 0xCAFEBABE (咖啡寶貝),非常容易記憶。

1.5 版本號

緊接著的字節是次版本號(minor_version)和主版本號(major_version),Java 的版本號從 45 開始,Java1.1 之后的 JDK 大版本發布主版本號向上加一(Java1.0~Java1.1 使用了 45.0~45.3 的版本號)。注意高版本的 JDK 能向下兼容 以前的 Class 文件,但不能運行以后版本的 Class 文件。

1.6 常量池

常量池可以理解為 Class 文件的資源倉庫,

主要存放:

  • 字面量(Literal)

  • 符號引用(Symbolic References)

    • 類和接口的全限定名(Full Qualified Name)
    • 字段的名稱描述符(Descriptor)
    • 方法的名稱和描述符
    類型 標識 描述
    CONSTANT_Class 7 類或接口的符號引用
    CONSTANT_Fieldref 9 字段的符號引用
    CONSTANT_Methodref 10 方法的符號引用
    CONSTANT_InterfaceMethodref 11 接口中方法的符號引用
    CONSTANT_String 8 字符串類型字面量
    CONSTANT_Integer 3 整型字面量
    CONSTANT_Float 4 浮點型字面量
    CONSTANT_Long 5 長整型字面量
    CONSTANT_Double 6 雙精度浮點型字面量
    CONSTANT_NameAndType 12 字段或方法的部分符號引用
    CONSTANT_Utf8 1 UTF-8 編碼字符串
    CONSTANT_MethodHandle 15 標識方法句柄
    CONSTANT_MethodType 16 標識方法類型
    CONSTANT_InvokeDynamic 18 動態方法調用點

1.7 訪問標識(access_flags)

用于識別類和接口層次的訪問信息

Flag Name Value Interpretation
ACC_PUBLIC 0x0001 是否為被聲明為 public ,可以被其他外部包中訪問
ACC_FINAL 0x0010 是否被聲明 final,不能派生子類
ACC_SUPER 0x0020 Treat superclass methods specially when invoked by the invokespecial instruction.
ACC_INTERFACE 0x0200 標識一個接口
ACC_ABSTRACT 0x0400 聲明 abstract,抽象類,不能實例化
ACC_SYNTHETIC 0x1000 聲明 synthetic; 標識這個類并非有用戶代碼產生
ACC_ANNOTATION 0x2000 標識這個一個注解
ACC_ENUM 0x4000 標識這是一個枚舉

1.8 類索引、父類索引和接口索引

Class 文件就是由這三項數據來確定這個類的繼承關系。類索引用于確定類的全限定類名,父索引用于確定父類的全限定類名,接口索引集合用于描述類實現了那些接口。

1.9 字段表集合

字段表集合[field_info] 用于描述接口或者類中聲明的變量。字段(field) 包括類變量和實例變量,但不包括方法內部聲明的局部變量。

  • 字段表結構

    field_info {
        u2             access_flags; //訪問標識
        u2             name_index; //名稱索引
        u2             descriptor_index;  //描述符索引
        u2             attributes_count;  //屬性計數器
        attribute_info attributes[attributes_count];  //屬性表
    }
    
  • 字段包含的信息:

    • 作用域(public 、private、protected 修飾符)
    • static 修飾符
    • 可變性 final
    • 并發可見性 volatile
    • 可否序列化 transient
    • 字段類型 【基本數據類型(byte、char、short、int、long 、float、double、boolean)、對象、數組】
  • 字段訪問標志

    ACC_PUBLIC 0x0001 Declared public; may be accessed from outside its package.
    ACC_PRIVATE 0x0002 聲明 private;
    ACC_PROTECTED 0x0004 聲明 protected;
    ACC_STATIC 0x0008 聲明 static.
    ACC_FINAL 0x0010 聲明 final;
    ACC_VOLATILE 0x0040 聲明 volatile;
    ACC_TRANSIENT 0x0080 聲明 transient;
    ACC_SYNTHETIC 0x1000 聲明 synthetic; 字段是否有編譯器自動產生的
    ACC_ENUM 0x4000 聲明字段是否是枚舉
  • 簡單名稱:沒有類型和參數修飾的方法或者字段名稱,如 inc()和 m 字段的簡稱為 inc 和 m

  • 全限定名:com/demo/TestClass; “;”標識類的全限定名結束

  • 描述符:用于描述字段的數據類型,方法的參數列表(數量、類型、順序)和返回值

    標識字符 代表類型 描述
    B byte 基本類型 byte
    C char 基本類型 char
    D double 基本類型 double
    F float 基本類型 float
    I int 基本類型 int
    J long 基本類型 long
    L ClassName ; reference 對象類型,如 : Ljava/lang/Object
    S short 基本類型 short
    Z boolean j 基本類型 boolean
    [ reference 數組類型 ,如數組int[] 被記錄為 [I,數組String[][]被記錄為 [[java/lang/String
    V void 特殊類型 Void

描述符來描述方法時,按照先參數列表,后返回值的順序描述;如:java.lang.String.toString() 描述為 () Ljava/lang/String,java.lang.String#valueOf(char[], int, int) 描述為 ([CII)Ljava/lang/String

1.10 方法表集合

方法描述采取與字段描述完全一致的方式。

  • 方法表結構

    method_info {
        u2             access_flags;
        u2             name_index;
        u2             descriptor_index;
        u2             attributes_count;
        attribute_info attributes[attributes_count];
    }
    
  • 相關訪問標識

    Flag Name Value Interpretation
    ACC_PUBLIC 0x0001 方法是否 public
    ACC_PRIVATE 0x0002 方法是否 private
    ACC_PROTECTED 0x0004 方法是否 protected;
    ACC_STATIC 0x0008 方法是否 static.
    ACC_FINAL 0x0010 方法是否 final;
    ACC_SYNCHRONIZED 0x0020 方法是否 synchronized; 標識同步方法
    ACC_BRIDGE 0x0040 標識是否由編譯器生成的橋接方法
    ACC_VARARGS 0x0080 方法是否接受不定參數
    ACC_NATIVE 0x0100 方法是否 native;
    ACC_ABSTRACT 0x0400 方法是否 abstract;
    ACC_STRICT 0x0800 方法是否 strictfp;
    ACC_SYNTHETIC 0x1000 方法是否為 synthetic;
  • 方法里定義的代碼

    方法里面的代碼,經過編譯器編譯成字節指令后,存放在方法屬性表集合,名為 Code 屬性里。

1.11 屬性表集合在

屬性表(attribute_info)在 Class 文件、字段表、方法表中都可以攜帶自己的屬性表集合,用于描述某些場景專有的信息。

  • 格式結構

    attribute_info {
        u2 attribute_name_index;
        u4 attribute_length;
        u1 info[attribute_length];
    }
    
  • 虛擬機預定義屬性

    屬性 位置 含義 class 版本
    SourceFile ClassFile 記錄源文件名稱 45.3
    InnerClasses ClassFile 內部類列表 45.3
    EnclosingMethod ClassFile 僅當一個類為局部類或匿名類時才能擁有這個屬性,這個屬性用于標識這個類所在的外圍方法 49.0
    SourceDebugExtension ClassFile JDK 1.6 中新增的屬性,SourccDcbugExtcnsion 屬性用于在儲額外的調試信息。譬如在進行 JSP 文件調試時,無法通過 Java 堆棧來定位到 JSP 文件的行號, JSR-45 規范為這些非 Java 語言編寫,卻需要編譯成字節碼并運行在 Java 虛擬機中的程序提供了一個進行調試的標準機制,使用 SourccDcbugExtcnsion 屬性就可以用于存儲這個標準所新加入的調試信息 49.0
    BootstrapMethods ClassFile JDK1.7 新增的屬性,用于保存 invokedynamic 指令引用的引導方法限定符 51.0
    ConstantValue field_info final 關鍵字定義的常量值 45.3
    Code method_info Java 代碼編譯成的字節碼指令 45.3
    Exceptions method_info 方法拋出的異常 45.3
    RuntimeVisibleParameterAnnotations, RuntimeInvisibleParameterAnnotations method_info JDK5 中新增的屬性,作用于方法參數 RuntimeVisibleParameterAnnotations屬性指明哪些注解是運行時可見;RuntimeInvisibleAnnotations`屬性指明哪里注解是運行時不可見的 49.0
    AnnotationDefault method_info JDK1.5 中新增的屬性,用于記錄注解類元素的默認值 49.0
    MethodParameters method_info MethodParameters 屬性記錄方法的形式參數的信息,比如方法名稱。 52.0
    Synthetic ClassFile, field_info, method_info 標識方法或字段為編譯器自動生成的 45.3
    Deprecated ClassFile, field_info, method_info 被聲明為 Deprecated 的方法和字段 45.3
    Signature ClassFile, field_info, method_info 記錄類,接口,構造函數,方法或字段的簽名 49.0
    RuntimeVisibleAnnotations, RuntimeInvisibleAnnotations ClassFile, field_info, method_info JDK5 中新增的屬性,為動態注解提供支持。RuntimeVisibleAnnotations屬性指明哪些注解是運行時可見;RuntimeInvisibleAnnotations屬性指明哪里注解是運行時不可見的 49.0
    LineNumberTable Code LineNumberTable 屬性表存放方法的行號信息 45.3
    LocalVariableTable Code LocalVariableTable 屬性表中存放方法的局部變量信息 45.3
    LocalVariableTypeTable Code JDK 1.5 中新增的屈件,它使用特征簽名代替描述符,是為了引入泛型語法之后能描述泛型參數化類型而添加 49.0
    StackMapTable Code JDKL6 中新增的屬性.供新的類型檢查驗證器 (Type Checker)檢查和處理目標方法的后部變量和操作數棧所需要的類型是否匹配 50.0
    RuntimeVisibleTypeAnnotations, RuntimeInvisibleTypeAnnotations ClassFile, field_info, method_info, Code jdk8 新增屬性<br />RuntimeVisibleTypeAnnotations:運行時可見類型注解<br />RuntimeInvisibleTypeAnnotations:運行時不可見類型注解 52.0
  • Code 屬性

    Java 程序方法體中的代碼經過 Javac 編譯器處理后,最終成為字節碼指令存儲在 Code 屬性內。注意并不是所有方法表都存在 Code 屬性,例如,接口和抽象類中的方法就不存在 Code 屬性。

  • Code 屬性格式定義

    Code_attribute {
        u2 attribute_name_index;  //指向常量CONSTANT_UTF8_info的索引,常量固定值為Code
        u4 attribute_length;
        u2 max_stack;   //操作數棧
        u2 max_locals;  //局部變量表所需的存儲空間
    
        //字節碼長度,最大值可達2^32-1, 但是虛擬機限制了一個方法不允許超過65535條字節碼指令
        //即使用了u2 的長度,超出這個限制會導致編譯失敗
        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 attributes_count;
        attribute_info attributes[attributes_count];
    }
    

二、字節碼指令

2.1 加載和存儲指令

加載和存儲指令用于將數據在棧幀中的局部變量表與操作數棧之間傳輸。

  • 將局部變量加載到操作數棧

    // i  代表對int 操作
    // l  代表對long 操作
    // f 代表對float 操作
    // d 代表對double 操作
    // a 代表對引用reference 操作
    // iload_<n>  代表一組指令,iload_0、iload_1、iload_2、iload_3等指令
    iload
    iload_<n>
    lload
    lload_<n>
    fload
    fload_<n>
    dload
    dload_<n>
    aload
    aload_<n>
    
  • 將數值從操作數棧存儲到局部變量表

    istore
    istore_<n>
    lstore
    lstore_<n>
    fstore
    fstore_<n>
    dstore
    dstore_<n>
    astore
    astore_<n>
    
  • 將常量加載到操作數棧

    bipush
    sipush
    ldc
    ldc_w
    ldc2_w
    aconst_null
    iconst_ml
    iconst_<i>
    lconst_<l>
    fconst_<f>
    dconst_<d>
    
  • 擴充局部變量表的訪問索引的指令:wide

2.2 運算指令

相關指令
  • 加法指令

    iadd、ladd、fadd、dadd
    
  • 減法指令

    isub、 lsub、 fsub、 dsub
    
  • 乘法指令

    imul、 lmul、 fmul、 dmul
    
  • 除法指令

    idiv、 ldiv、 fdiv、 ddiv
    
  • 求余指令

     irem、 lrem、 frem、 drem
    
  • 取反指令

    ineg、 lneg、 fneg、 dneg
    
  • 位移指令

    ishl、 isbr、 iusbr、 lsbl、 lshr、 lushr
    
  • 按位或指令

    ior、 lor
    
  • 按位與指令

    iand、 land
    
  • 按位異或指令

    ixor、 lxor
    
  • 局部變量自增指令

    iinc
    
  • 比較指令

    dcmpg、 dcmpl、 fcmpg、 fcmpl、 lcmp
    
注意
  • 只有當除法指令和求余指令遇到除數為零時,虛擬機會拋出 ArithmeticException 異常
  • Java 在處理浮點數運算時,不會拋出任何運行異常(Java 語言的異常)
  • 當一個操作產生溢出時,將使用有符號的無窮大表示,如果某個操作結果沒有明確的數學定義的話,將會使用 NaN 表示
  • 所有使用 NaN 值作為操作數的算術操作,結果都返回 NaN
double a = 1;
double b = a / 0;  //不會報錯,結果Infinity

double a = 0.0;
double b = a / 0.0; //不會報錯,結果NaN

2.3 類型轉換指令

類型轉換指令可以將兩種不同的數值類型進行互相轉換,一般用于用戶代碼中的顯示類型轉換操作,隱式類型轉換不同轉換指令,虛擬機直接支持。

  • 顯示類型轉換指令

    i2b  int 轉換byte
    i2c  int 轉換char
    i2s    int 轉換short
    l2i  long 轉換 int
    f2i  float 轉換 int
    f2l  float 轉換 long
    d2i  double 轉換 int
    d2l  double 轉換 long
    d2f  double 轉換 float
    
  • 轉換規則

    • 如果浮點值是 NaN, 那轉換結果就是 int 或者 long 類型的 0
    • 如果浮點值不是無窮大的話,浮點值使用 IEEE 754 的向零舍入模式去整,獲取整數值 v,如果 v 在目標類型 T(int 或 long) 的標識表示范圍之內,那轉換結果就是 v。
    • 否則,將根據 v 的符號,轉換為 T 所能表示的最大或最小正數。
    double nan = 0.0 / 0.0;
    int a = (int) nan;
    System.out.println(a);  //0
    
    float b = (float) nan;
    System.out.println(b); //NaN
    

2.4 對象創建與訪問指令

  • 創建類實例指令

    new
    
  • 創建數組指令

    newarray
    anewarray
    multianewarray
    
  • 訪問類字段 和 實例字段

    getfield
    putfield
    
    getstatic
    putstatic
    
  • 加載數組元素到操作數棧

    baload  //byte數組
    caload  //char數組
    saload  //short數組
    iaload  //int數組
    laload //long 數組
    faload //float 數組
    daload //double 數組
    aaload //對象數組
    
  • 將操作數棧存儲到數組元素中

    bastore
    castore
    sastore
    iastore
    lastore
    fastore
    dastore
    aastore
    
  • 獲取數組長度

    arraylength
    
  • 檢查類實例類型的指令

    instanceof
    checkcast
    

2.5 操作數棧的管理指令

  • 出棧指令

    pop
    pop2 //出棧2個元素
    
  • 復制棧頂一個或者兩個數值并復制或雙份的復制值重新壓入棧頂

    dup
    dup2
    dup_x1
    dup2_x1
    dup_x2
    dup2_x2
    
  • 將棧最頂端的兩個數值互換

    swap
    

2.6 控制轉移指令

  • 條件分支

    ifeq
    iflt
    ifle
    ifne
    ifge
    ifnull
    ifnonull
    if_icmpeq  比較棧頂兩個int類型數值的大小 ,當前者  等于  后者時,跳轉
    if_icmpne
    if_icmplt
    if_icmpgt
    if_icmple
    if_icmpge
    if_acmpeq
    if_acmpne
    
  • 復合條件分支

    tableswitch   switch 條件跳轉 case值連續
    lookupswitch  witch 條件跳轉 case值不連續
    
  • 無條件分支

    goto 無條件跳轉
    goto_w 無條件跳轉  寬索引
    jsr   SE6之前 finally字句使用 跳轉到指定16位的offset,并將jsr下一條指令地址壓入棧頂
    jsr_w SE6之前 同上  寬索引
    ret  SE6之前返回由指定的局部變量所給出的指令地址(一般配合jsr  jsr_w使用)
    w同局部變量的寬索引含義
    

2.7 方法調用和返回指令

  • 方法調用指令

    
    invokevirtual: 調用對象實例方法
    invokeinterface 調用接口方法
    invokespecial 調用一些需要特需處理的實例方法,包括實例初始化方法、私有方法、父類方法
    invokestatic  調用類方法
    invokedynamic  在運行時動態解析出調用點限定符所引用的方法,并執行
    
  • 返回指令

    ireturn
    lreturn
    freturn
    dreturn
    areturn
    return 聲明為void 的方法
    

2.8 異常處理指令

athrow  顯示拋出異常

2.9 同步指令

Java 虛擬機可以支持方法級別的同步和方法內部一段指令序列的同步,這兩種同步結構都使用管理(Monitor)來支持。

  • 方法級別的同步是由方法表結構中 ACC_SYNCHRONIZED 訪問標識來處理

  • 方法內部一段指令序列的同步

    monitorenter  獲取鎖,進入代碼塊
    monitorexit   釋放鎖,必須與monitorenter成對出現
    
  • 源碼

    public class SynchronizedInstruction {
        private Object lock=new Object();
        void onlyMe(Object lock){
            synchronized (lock){
                //doSomething
            }
        }
    }
    
  • 反匯編

    Compiled from "SynchronizedInstruction.java"
    public class cn.hdj.jvm.bytecode.SynchronizedInstruction {
      private java.lang.Object lock;
      public cn.hdj.jvm.bytecode.SynchronizedInstruction();
      void onlyMe(java.lang.Object);
    }
    Classfile /home/hdj/IDEA/Java-Learning/src/main/java/cn/hdj/jvm/bytecode/SynchronizedInstruction.class
      Last modified 2021-3-20; size 488 bytes
      MD5 checksum 1f6db0fa955b6d719018d2ea50e1e910
      Compiled from "SynchronizedInstruction.java"
    public class cn.hdj.jvm.bytecode.SynchronizedInstruction
      SourceFile: "SynchronizedInstruction.java"
      minor version: 0
      major version: 51
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #2.#19         //  java/lang/Object."<init>":()V
       #2 = Class              #20            //  java/lang/Object
       #3 = Fieldref           #4.#21         //  cn/hdj/jvm/bytecode/SynchronizedInstruction.lock:Ljava/lang/Object;
       #4 = Class              #22            //  cn/hdj/jvm/bytecode/SynchronizedInstruction
       #5 = Utf8               lock
       #6 = Utf8               Ljava/lang/Object;
       #7 = Utf8               <init>
       #8 = Utf8               ()V
       #9 = Utf8               Code
      #10 = Utf8               LineNumberTable
      #11 = Utf8               onlyMe
      #12 = Utf8               (Ljava/lang/Object;)V
      #13 = Utf8               StackMapTable
      #14 = Class              #22            //  cn/hdj/jvm/bytecode/SynchronizedInstruction
      #15 = Class              #20            //  java/lang/Object
      #16 = Class              #23            //  java/lang/Throwable
      #17 = Utf8               SourceFile
      #18 = Utf8               SynchronizedInstruction.java
      #19 = NameAndType        #7:#8          //  "<init>":()V
      #20 = Utf8               java/lang/Object
      #21 = NameAndType        #5:#6          //  lock:Ljava/lang/Object;
      #22 = Utf8               cn/hdj/jvm/bytecode/SynchronizedInstruction
      #23 = Utf8               java/lang/Throwable
    {
      private java.lang.Object lock;
        flags: ACC_PRIVATE
    
      public cn.hdj.jvm.bytecode.SynchronizedInstruction();
        flags: ACC_PUBLIC
        Code:
          stack=3, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: aload_0
             5: new           #2                  // class java/lang/Object
             8: dup
             9: invokespecial #1                  // Method java/lang/Object."<init>":()V
            12: putfield      #3                  // Field lock:Ljava/lang/Object;
            15: return
          LineNumberTable:
            line 8: 0
            line 9: 4
    
      void onlyMe(java.lang.Object);
        flags:
        Code:
          stack=2, locals=4, args_size=2
             0: aload_1       //將lock對象入棧
             1: dup           //復制棧頂元素
             2: astore_2      //將棧頂元素存儲到局部變量表Slot2中
             3: monitorenter  //以lock對象為鎖,開始同步
             4: aload_2       //將局部變量表Slot2中元素入棧
             5: monitorexit   //退出同步
             6: goto          14  //程序正常結束,跳轉到14返回
             9: astore_3      //從這步開始是異常路徑,開下面的Exception table
            10: aload_2       //將局部變量表Slot2中元素入棧
            11: monitorexit   //退出同步
            12: aload_3       //將局部變量表Slot3中元素(異常對象)入棧
            13: athrow        //把異常對象重新拋出個onlyMe方法調用者
            14: return        //方法返回
          Exception table:
             from    to  target type
                 4     6     9   any
                 9    12     9   any
          LineNumberTable:
            line 11: 0
            line 13: 4
            line 14: 14
          StackMapTable: number_of_entries = 2
               frame_type = 255 /* full_frame */
              offset_delta = 9
              locals = [ class cn/hdj/jvm/bytecode/SynchronizedInstruction, class java/lang/Object, class java/lang/Object ]
              stack = [ class java/lang/Throwable ]
               frame_type = 250 /* chop */
              offset_delta = 4
    
    }
    
    

三、例子解析

  • 代碼
public class DemoDynamic {
    public static void foo() {
        int a = 1;
        int b = 2;
        int c = (a + b) * 5;
    }
}
  • javap 命令(也可以使用 IDEA 查看字節碼工具:jclasslib)

    javac -g -encoding utf-8 DemoDynamic.java
    javap -verbose -c .\DemoDynamic.class >  .\DemoDynamic.javap
    
  • 字節文件

    Classfile /D:/IDEA/Java-Learning/src/main/java/cn/hdj/jvm/bytecode/DemoDynamic.class
      Last modified 2020-10-17; size 419 bytes
      MD5 checksum 0242e2d86e94eb62d302f5a034336416
      Compiled from "DemoDynamic.java"
    public class cn.hdj.jvm.bytecode.DemoDynamic
      minor version: 0  //版本號
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER  //訪問標識符
    Constant pool:   //常量池
       #1 = Methodref          #3.#18         // java/lang/Object."<init>":()V
       #2 = Class              #19            // cn/hdj/jvm/bytecode/DemoDynamic
       #3 = Class              #20            // java/lang/Object
       #4 = Utf8               <init>
       #5 = Utf8               ()V
       #6 = Utf8               Code
       #7 = Utf8               LineNumberTable
       #8 = Utf8               LocalVariableTable
       #9 = Utf8               this
      #10 = Utf8               Lcn/hdj/jvm/bytecode/DemoDynamic;
      #11 = Utf8               foo
      #12 = Utf8               a
      #13 = Utf8               I
      #14 = Utf8               b
      #15 = Utf8               c
      #16 = Utf8               SourceFile
      #17 = Utf8               DemoDynamic.java
      #18 = NameAndType        #4:#5          // "<init>":()V
      #19 = Utf8               cn/hdj/jvm/bytecode/DemoDynamic
      #20 = Utf8               java/lang/Object
    {
      public cn.hdj.jvm.bytecode.DemoDynamic(); //默認的構造方法
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
        //棧容量1 , 局部變量表容量1, 參數個數1(因為每個實例方法都會有一個隱藏參數this)
          stack=1, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: return
          LineNumberTable:
            line 6: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       5     0  this   Lcn/hdj/jvm/bytecode/DemoDynamic;
    
      public static void foo();  //foo()    方法
        descriptor: ()V
        flags: ACC_PUBLIC, ACC_STATIC  //標識符,public static
        Code:  //方法表中Code 屬性
          stack=2, locals=3, args_size=0  //棧容量2 , 局部變量表容量3, 參數個數0
             0: iconst_1  //  將常量值1入棧->  棧1=1
             1: istore_0  //  將棧頂元素存儲到局部變量表Slot1位置  -> 局部0=1
             2: iconst_2  //  將常量值2入棧 ->  棧1=2
             3: istore_1  //  將棧頂元素存儲到局部變量表Slot2位置    ->  局部1=2
             4: iload_0   //  將局部變量表Slot1中元素入棧
             5: iload_1     // 將局部變量表Slot2中元素入棧
             6: iadd        // 執行相加操作, 1+2 = 3, 入棧
             7: iconst_5    // 將常量值5入棧
             8: imul        // 執行相乘操作,3*5=15,入棧
             9: istore_2    // 將棧頂元素存儲到局部變量表Slot2位置-> 局部2=15
            10: return    //返回
          LineNumberTable: //行數表
            line 9: 0
            line 10: 2
            line 11: 4
            line 12: 10
          LocalVariableTable:  //局部變量表
            Start  Length  Slot  Name   Signature
                2       9     0     a   I
                4       7     1     b   I
               10       1     2     c   I
    }
    SourceFile: "DemoDynamic.java"
    
    
    1602932223498-945875c1-116c-425e-9ba7-17ff982d10e2

四、字節碼增強

具體詳情看 字節碼增強技術探索,這里只簡單列出相關工具及使用場景。

12e1964581f38f04488dfc6d2f84f003110966

4.1 ASM

對于需要手動操縱字節碼的需求,可以使用 ASM,它可以直接生產 .class 字節碼文件,也可以在類被加載入 JVM 之前動態修改類行為

3c40b90c6d92499ad4c708162095fe3029983
  • ASM 工具 輔助工具

4.2   Javassist

利用 Javassist 實現字節碼增強時,可以無須關注字節碼刻板的結構,其優點就在于編程簡單。直接使用 java 編碼的形式,而不需要了解虛擬機指令,就能動態改變類的結構或者動態生成類。

4.3 Instrument

instrument 是 JVM 提供的一個可以修改已加載類的類庫,專門為 Java 語言編寫的插樁服務提供支持。它需要依賴 JVMTI 的 Attach API 機制實現。注意:ASM 和 Javassist 操作字節碼庫只能在類加載前對類進行強化。

4.5 字節碼增強技術使用場景

  • AOP 面向切面編程

  • 熱部署:不部署服務而對線上服務做修改,可以做打點、增加日志等操作。

  • Mock:測試時候對某些服務做 Mock。

  • 性能診斷工具:比如 bTrace 就是利用 Instrument,實現無侵入地跟蹤一個正在運行的 JVM,監控到類和方法級別的狀態信息。

參考

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,621評論 2 380

推薦閱讀更多精彩內容