经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 其他 » 职业生涯 » 查看文章
JVM学习笔记之class文件结构【七】
来源:cnblogs  作者:JiaJianHuang  时间:2021/3/29 9:00:15  对本文有异议

一、概念

1.1 无符号数:

以 u1、u2、u3、u4、u8 代表 1 个字节,2 个字节、4 个字节、8 个字节的无符号数。无符号数可以描述数字,索引引用、数量值和按照 UTF-8 编码构成的字符串值。

1.2 表

  • 表是由多个无符号数或其他表作为数据项构成的复合的数据结构,所有表都习惯性的以“_info”结尾。表用于表示有层次关系的复合结构的数据,整个 Class 文件本质上是一张表

1.3 class 文件组成

  1. ClassFile {
  2. u4 magic; //魔数, 用于识别class文件格式
  3. u2 minor_version;//次版本号
  4. u2 major_version;//主版本号
  5. u2 constant_pool_count; //常量池计数器
  6. cp_info constant_pool[constant_pool_count-1]; //常量池
  7. u2 access_flags;//访问标志
  8. u2 this_class;//类索引
  9. u2 super_class;//父类索引
  10. u2 interfaces_count;//接口计数器
  11. u2 interfaces[interfaces_count];//接口索引集合
  12. u2 fields_count;//字段计数器
  13. field_info fields[fields_count];//字段表集合
  14. u2 methods_count;//方法计数器
  15. method_info methods[methods_count];//方法表
  16. u2 attributes_count; //属性计数器
  17. attribute_info attributes[attributes_count];附加属性表
  18. }

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) 包括类变量和实例变量,但不包括方法内部声明的局部变量。

  • 字段表结构

    1. field_info {
    2. u2 access_flags; //访问标识
    3. u2 name_index; //名称索引
    4. u2 descriptor_index; //描述符索引
    5. u2 attributes_count; //属性计数器
    6. attribute_info attributes[attributes_count]; //属性表
    7. }
  • 字段包含的信息:

    • 作用域(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 方法表集合

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

  • 方法表结构

    1. method_info {
    2. u2 access_flags;
    3. u2 name_index;
    4. u2 descriptor_index;
    5. u2 attributes_count;
    6. attribute_info attributes[attributes_count];
    7. }
  • 相关访问标识

    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 文件、字段表、方法表中都可以携带自己的属性表集合,用于描述某些场景专有的信息。

  • 格式结构

    1. attribute_info {
    2. u2 attribute_name_index;
    3. u4 attribute_length;
    4. u1 info[attribute_length];
    5. }
  • 虚拟机预定义属性

    属性 位置 含义 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 属性格式定义

    1. Code_attribute {
    2. u2 attribute_name_index; //指向常量CONSTANT_UTF8_info的索引,常量固定值为Code
    3. u4 attribute_length;
    4. u2 max_stack; //操作数栈
    5. u2 max_locals; //局部变量表所需的存储空间
    6. //字节码长度,最大值可达2^32-1, 但是虚拟机限制了一个方法不允许超过65535条字节码指令
    7. //即使用了u2 的长度,超出这个限制会导致编译失败
    8. u4 code_length;
    9. u1 code[code_length]; //字节码指令的子节流
    10. u2 exception_table_length;
    11. { u2 start_pc;
    12. u2 end_pc;
    13. u2 handler_pc;
    14. u2 catch_type;
    15. } exception_table[exception_table_length];
    16. u2 attributes_count;
    17. attribute_info attributes[attributes_count];
    18. }

二、字节码指令

2.1 加载和存储指令

加载和存储指令用于将数据在栈帧中的局部变量表与操作数栈之间传输。

  • 将局部变量加载到操作数栈

    1. // i 代表对int 操作
    2. // l 代表对long 操作
    3. // f 代表对float 操作
    4. // d 代表对double 操作
    5. // a 代表对引用reference 操作
    6. // iload_<n> 代表一组指令,iload_0、iload_1、iload_2、iload_3等指令
    7. iload
    8. iload_<n>
    9. lload
    10. lload_<n>
    11. fload
    12. fload_<n>
    13. dload
    14. dload_<n>
    15. aload
    16. aload_<n>
  • 将数值从操作数栈存储到局部变量表

    1. istore
    2. istore_<n>
    3. lstore
    4. lstore_<n>
    5. fstore
    6. fstore_<n>
    7. dstore
    8. dstore_<n>
    9. astore
    10. astore_<n>
  • 将常量加载到操作数栈

    1. bipush
    2. sipush
    3. ldc
    4. ldc_w
    5. ldc2_w
    6. aconst_null
    7. iconst_ml
    8. iconst_<i>
    9. lconst_<l>
    10. fconst_<f>
    11. dconst_<d>
  • 扩充局部变量表的访问索引的指令:wide

2.2 运算指令

相关指令
  • 加法指令

    1. iaddladdfadddadd
  • 减法指令

    1. isub lsub fsub dsub
  • 乘法指令

    1. imul lmul fmul dmul
  • 除法指令

    1. idiv ldiv fdiv ddiv
  • 求余指令

    1. irem lrem frem drem
  • 取反指令

    1. ineg lneg fneg dneg
  • 位移指令

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

    1. ior lor
  • 按位与指令

    1. iand land
  • 按位异或指令

    1. ixor lxor
  • 局部变量自增指令

    1. iinc
  • 比较指令

    1. dcmpg dcmpl fcmpg fcmpl lcmp
注意
  • 只有当除法指令和求余指令遇到除数为零时,虚拟机会抛出 ArithmeticException 异常
  • Java 在处理浮点数运算时,不会抛出任何运行异常(Java 语言的异常)
  • 当一个操作产生溢出时,将使用有符号的无穷大表示,如果某个操作结果没有明确的数学定义的话,将会使用 NaN 表示
  • 所有使用 NaN 值作为操作数的算术操作,结果都返回 NaN
  1. double a = 1;
  2. double b = a / 0; //不会报错,结果Infinity
  3. double a = 0.0;
  4. double b = a / 0.0; //不会报错,结果NaN

2.3 类型转换指令

类型转换指令可以将两种不同的数值类型进行互相转换,一般用于用户代码中的显示类型转换操作,隐式类型转换不同转换指令,虚拟机直接支持。

  • 显示类型转换指令

    1. i2b int 转换byte
    2. i2c int 转换char
    3. i2s int 转换short
    4. l2i long 转换 int
    5. f2i float 转换 int
    6. f2l float 转换 long
    7. d2i double 转换 int
    8. d2l double 转换 long
    9. d2f double 转换 float
  • 转换规则

    • 如果浮点值是 NaN, 那转换结果就是 int 或者 long 类型的 0
    • 如果浮点值不是无穷大的话,浮点值使用 IEEE 754 的向零舍入模式去整,获取整数值 v,如果 v 在目标类型 T(int 或 long) 的标识表示范围之内,那转换结果就是 v。
    • 否则,将根据 v 的符号,转换为 T 所能表示的最大或最小正数。
    1. double nan = 0.0 / 0.0;
    2. int a = (int) nan;
    3. System.out.println(a); //0
    4. float b = (float) nan;
    5. System.out.println(b); //NaN

2.4 对象创建与访问指令

  • 创建类实例指令

    1. new
  • 创建数组指令

    1. newarray
    2. anewarray
    3. multianewarray
  • 访问类字段 和 实例字段

    1. getfield
    2. putfield
    3. getstatic
    4. putstatic
  • 加载数组元素到操作数栈

    1. baload //byte数组
    2. caload //char数组
    3. saload //short数组
    4. iaload //int数组
    5. laload //long 数组
    6. faload //float 数组
    7. daload //double 数组
    8. aaload //对象数组
  • 将操作数栈存储到数组元素中

    1. bastore
    2. castore
    3. sastore
    4. iastore
    5. lastore
    6. fastore
    7. dastore
    8. aastore
  • 获取数组长度

    1. arraylength
  • 检查类实例类型的指令

    1. instanceof
    2. checkcast

2.5 操作数栈的管理指令

  • 出栈指令

    1. pop
    2. pop2 //出栈2个元素
  • 复制栈顶一个或者两个数值并复制或双份的复制值重新压入栈顶

    1. dup
    2. dup2
    3. dup_x1
    4. dup2_x1
    5. dup_x2
    6. dup2_x2
  • 将栈最顶端的两个数值互换

    1. swap

2.6 控制转移指令

  • 条件分支

    1. ifeq
    2. iflt
    3. ifle
    4. ifne
    5. ifge
    6. ifnull
    7. ifnonull
    8. if_icmpeq 比较栈顶两个int类型数值的大小 ,当前者 等于 后者时,跳转
    9. if_icmpne
    10. if_icmplt
    11. if_icmpgt
    12. if_icmple
    13. if_icmpge
    14. if_acmpeq
    15. if_acmpne
  • 复合条件分支

    1. tableswitch switch 条件跳转 case值连续
    2. lookupswitch witch 条件跳转 case值不连续
  • 无条件分支

    1. goto 无条件跳转
    2. goto_w 无条件跳转 宽索引
    3. jsr SE6之前 finally字句使用 跳转到指定16位的offset,并将jsr下一条指令地址压入栈顶
    4. jsr_w SE6之前 同上 宽索引
    5. ret SE6之前返回由指定的局部变量所给出的指令地址(一般配合jsr jsr_w使用)
    6. w同局部变量的宽索引含义

2.7 方法调用和返回指令

  • 方法调用指令

    1. invokevirtual: 调用对象实例方法
    2. invokeinterface 调用接口方法
    3. invokespecial 调用一些需要特需处理的实例方法,包括实例初始化方法、私有方法、父类方法
    4. invokestatic 调用类方法
    5. invokedynamic 在运行时动态解析出调用点限定符所引用的方法,并执行
  • 返回指令

    1. ireturn
    2. lreturn
    3. freturn
    4. dreturn
    5. areturn
    6. return 声明为void 的方法

2.8 异常处理指令

  1. athrow 显示抛出异常

2.9 同步指令

Java 虚拟机可以支持方法级别的同步和方法内部一段指令序列的同步,这两种同步结构都使用管理(Monitor)来支持。

  • 方法级别的同步是由方法表结构中 ACC_SYNCHRONIZED 访问标识来处理

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

    1. monitorenter 获取锁,进入代码块
    2. monitorexit 释放锁,必须与monitorenter成对出现
  • 源码

    1. public class SynchronizedInstruction {
    2. private Object lock=new Object();
    3. void onlyMe(Object lock){
    4. synchronized (lock){
    5. //doSomething
    6. }
    7. }
    8. }
  • 反汇编

    1. Compiled from "SynchronizedInstruction.java"
    2. public class cn.hdj.jvm.bytecode.SynchronizedInstruction {
    3. private java.lang.Object lock;
    4. public cn.hdj.jvm.bytecode.SynchronizedInstruction();
    5. void onlyMe(java.lang.Object);
    6. }
    7. Classfile /home/hdj/IDEA/Java-Learning/src/main/java/cn/hdj/jvm/bytecode/SynchronizedInstruction.class
    8. Last modified 2021-3-20; size 488 bytes
    9. MD5 checksum 1f6db0fa955b6d719018d2ea50e1e910
    10. Compiled from "SynchronizedInstruction.java"
    11. public class cn.hdj.jvm.bytecode.SynchronizedInstruction
    12. SourceFile: "SynchronizedInstruction.java"
    13. minor version: 0
    14. major version: 51
    15. flags: ACC_PUBLIC, ACC_SUPER
    16. Constant pool:
    17. #1 = Methodref #2.#19 // java/lang/Object."<init>":()V
    18. #2 = Class #20 // java/lang/Object
    19. #3 = Fieldref #4.#21 // cn/hdj/jvm/bytecode/SynchronizedInstruction.lock:Ljava/lang/Object;
    20. #4 = Class #22 // cn/hdj/jvm/bytecode/SynchronizedInstruction
    21. #5 = Utf8 lock
    22. #6 = Utf8 Ljava/lang/Object;
    23. #7 = Utf8 <init>
    24. #8 = Utf8 ()V
    25. #9 = Utf8 Code
    26. #10 = Utf8 LineNumberTable
    27. #11 = Utf8 onlyMe
    28. #12 = Utf8 (Ljava/lang/Object;)V
    29. #13 = Utf8 StackMapTable
    30. #14 = Class #22 // cn/hdj/jvm/bytecode/SynchronizedInstruction
    31. #15 = Class #20 // java/lang/Object
    32. #16 = Class #23 // java/lang/Throwable
    33. #17 = Utf8 SourceFile
    34. #18 = Utf8 SynchronizedInstruction.java
    35. #19 = NameAndType #7:#8 // "<init>":()V
    36. #20 = Utf8 java/lang/Object
    37. #21 = NameAndType #5:#6 // lock:Ljava/lang/Object;
    38. #22 = Utf8 cn/hdj/jvm/bytecode/SynchronizedInstruction
    39. #23 = Utf8 java/lang/Throwable
    40. {
    41. private java.lang.Object lock;
    42. flags: ACC_PRIVATE
    43. public cn.hdj.jvm.bytecode.SynchronizedInstruction();
    44. flags: ACC_PUBLIC
    45. Code:
    46. stack=3, locals=1, args_size=1
    47. 0: aload_0
    48. 1: invokespecial #1 // Method java/lang/Object."<init>":()V
    49. 4: aload_0
    50. 5: new #2 // class java/lang/Object
    51. 8: dup
    52. 9: invokespecial #1 // Method java/lang/Object."<init>":()V
    53. 12: putfield #3 // Field lock:Ljava/lang/Object;
    54. 15: return
    55. LineNumberTable:
    56. line 8: 0
    57. line 9: 4
    58. void onlyMe(java.lang.Object);
    59. flags:
    60. Code:
    61. stack=2, locals=4, args_size=2
    62. 0: aload_1 //将lock对象入栈
    63. 1: dup //复制栈顶元素
    64. 2: astore_2 //将栈顶元素存储到局部变量表Slot2中
    65. 3: monitorenter //以lock对象为锁,开始同步
    66. 4: aload_2 //将局部变量表Slot2中元素入栈
    67. 5: monitorexit //退出同步
    68. 6: goto 14 //程序正常结束,跳转到14返回
    69. 9: astore_3 //从这步开始是异常路径,开下面的Exception table
    70. 10: aload_2 //将局部变量表Slot2中元素入栈
    71. 11: monitorexit //退出同步
    72. 12: aload_3 //将局部变量表Slot3中元素(异常对象)入栈
    73. 13: athrow //把异常对象重新抛出个onlyMe方法调用者
    74. 14: return //方法返回
    75. Exception table:
    76. from to target type
    77. 4 6 9 any
    78. 9 12 9 any
    79. LineNumberTable:
    80. line 11: 0
    81. line 13: 4
    82. line 14: 14
    83. StackMapTable: number_of_entries = 2
    84. frame_type = 255 /* full_frame */
    85. offset_delta = 9
    86. locals = [ class cn/hdj/jvm/bytecode/SynchronizedInstruction, class java/lang/Object, class java/lang/Object ]
    87. stack = [ class java/lang/Throwable ]
    88. frame_type = 250 /* chop */
    89. offset_delta = 4
    90. }

三、例子解析

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

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

    1. Classfile /D:/IDEA/Java-Learning/src/main/java/cn/hdj/jvm/bytecode/DemoDynamic.class
    2. Last modified 2020-10-17; size 419 bytes
    3. MD5 checksum 0242e2d86e94eb62d302f5a034336416
    4. Compiled from "DemoDynamic.java"
    5. public class cn.hdj.jvm.bytecode.DemoDynamic
    6. minor version: 0 //版本号
    7. major version: 52
    8. flags: ACC_PUBLIC, ACC_SUPER //访问标识符
    9. Constant pool: //常量池
    10. #1 = Methodref #3.#18 // java/lang/Object."<init>":()V
    11. #2 = Class #19 // cn/hdj/jvm/bytecode/DemoDynamic
    12. #3 = Class #20 // java/lang/Object
    13. #4 = Utf8 <init>
    14. #5 = Utf8 ()V
    15. #6 = Utf8 Code
    16. #7 = Utf8 LineNumberTable
    17. #8 = Utf8 LocalVariableTable
    18. #9 = Utf8 this
    19. #10 = Utf8 Lcn/hdj/jvm/bytecode/DemoDynamic;
    20. #11 = Utf8 foo
    21. #12 = Utf8 a
    22. #13 = Utf8 I
    23. #14 = Utf8 b
    24. #15 = Utf8 c
    25. #16 = Utf8 SourceFile
    26. #17 = Utf8 DemoDynamic.java
    27. #18 = NameAndType #4:#5 // "<init>":()V
    28. #19 = Utf8 cn/hdj/jvm/bytecode/DemoDynamic
    29. #20 = Utf8 java/lang/Object
    30. {
    31. public cn.hdj.jvm.bytecode.DemoDynamic(); //默认的构造方法
    32. descriptor: ()V
    33. flags: ACC_PUBLIC
    34. Code:
    35. //栈容量1 , 局部变量表容量1, 参数个数1(因为每个实例方法都会有一个隐藏参数this)
    36. stack=1, locals=1, args_size=1
    37. 0: aload_0
    38. 1: invokespecial #1 // Method java/lang/Object."<init>":()V
    39. 4: return
    40. LineNumberTable:
    41. line 6: 0
    42. LocalVariableTable:
    43. Start Length Slot Name Signature
    44. 0 5 0 this Lcn/hdj/jvm/bytecode/DemoDynamic;
    45. public static void foo(); //foo() 方法
    46. descriptor: ()V
    47. flags: ACC_PUBLIC, ACC_STATIC //标识符,public static
    48. Code: //方法表中Code 属性
    49. stack=2, locals=3, args_size=0 //栈容量2 , 局部变量表容量3, 参数个数0
    50. 0: iconst_1 // 将常量值1入栈-> 栈1=1
    51. 1: istore_0 // 将栈顶元素存储到局部变量表Slot1位置 -> 局部0=1
    52. 2: iconst_2 // 将常量值2入栈 -> 栈1=2
    53. 3: istore_1 // 将栈顶元素存储到局部变量表Slot2位置 -> 局部1=2
    54. 4: iload_0 // 将局部变量表Slot1中元素入栈
    55. 5: iload_1 // 将局部变量表Slot2中元素入栈
    56. 6: iadd // 执行相加操作, 1+2 = 3, 入栈
    57. 7: iconst_5 // 将常量值5入栈
    58. 8: imul // 执行相乘操作,3*5=15,入栈
    59. 9: istore_2 // 将栈顶元素存储到局部变量表Slot2位置-> 局部2=15
    60. 10: return //返回
    61. LineNumberTable: //行数表
    62. line 9: 0
    63. line 10: 2
    64. line 11: 4
    65. line 12: 10
    66. LocalVariableTable: //局部变量表
    67. Start Length Slot Name Signature
    68. 2 9 0 a I
    69. 4 7 1 b I
    70. 10 1 2 c I
    71. }
    72. 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,监控到类和方法级别的状态信息。

参考

原文链接:http://www.cnblogs.com/JianJianHuang/p/14562043.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号