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 新增属性
    RuntimeVisibleTypeAnnotations:运行时可见类型注解
    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_  代表一组指令,iload_0、iload_1、iload_2、iload_3等指令
    iload
    iload_
    lload
    lload_
    fload
    fload_
    dload
    dload_
    aload
    aload_
    
  • 将数值从操作数栈存储到局部变量表

    istore
    istore_
    lstore
    lstore_
    fstore
    fstore_
    dstore
    dstore_
    astore
    astore_
    
  • 将常量加载到操作数栈

    bipush
    sipush
    ldc
    ldc_w
    ldc2_w
    aconst_null
    iconst_ml
    iconst_
    lconst_
    fconst_
    dconst_
    
  • 扩充局部变量表的访问索引的指令: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."":()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               
       #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          //  "":()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."":()V
             4: aload_0
             5: new           #2                  // class java/lang/Object
             8: dup
             9: invokespecial #1                  // Method java/lang/Object."":()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."":()V
       #2 = Class              #19            // cn/hdj/jvm/bytecode/DemoDynamic
       #3 = Class              #20            // java/lang/Object
       #4 = Utf8               
       #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          // "":()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."":()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 工具 辅助工具
    • IDEA 插件 ASM ByteCode Outline,用于查看类中的代码对应的 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,监控到类和方法级别的状态信息。

参考

  • 《深入了解 Java 虚拟机 Java 高级特性和最佳实践》
  • https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html
  • 虚拟机指令 https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5
  • https://www.cnblogs.com/noteless/p/9556928.html
  • 字节码增强技术探索 https://tech.meituan.com/2019/09/05/java-bytecode-enhancement.html
  • 轻松看懂 Java 字节码 https://juejin.im/post/6844903588716609543

你可能感兴趣的:(JVM学习笔记之class文件结构【七】)