Class信息及操作

类结构

  • 头部信息
  • 常量池
  • 类信息部分
  • 字段信息
  • 方法信息
  • 属性信息
class ClassFile {
	/**
     ***********************************************************
     * 1. class头部信息部分
     ***********************************************************
	 */	
    u4             magic;			 //标记为class文件的魔数,固定为 0xCAFEBABE
    u2             minor_version;    //class文件的次版本号
    u2             major_version;    //class文件的主版本号
    /*
     ***********************************************************
     * 2. 常量池部分
     ***********************************************************
     */
    u2             constant_pool_count;						//常量池中常量表个数
    cp_info        constant_pool[constant_pool_count-1];	//常量池数据

	/**
     ***********************************************************
     * 3. class信息部分
     ***********************************************************
	 */
    u2             access_flags;							//类的访问权限
    u2             this_class;								//
    u2             super_class;								//
    u2             interfaces_count;						//
    u2             interfaces[interfaces_count];			//
    /**
     ***********************************************************
     * 4. class字段部分
     ***********************************************************
	 */
    u2             fields_count;
    field_info     fields[fields_count];
    /**
     ***********************************************************
     * 5. class方法部分
     ***********************************************************
	 */
    u2             methods_count;
    method_info    methods[methods_count];
    /**
     ***********************************************************
     * 6. class属性部分
     ***********************************************************
	 */
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}

1. 头部信息

  • magic 魔法数
  • minor_version class文件的子版本号
  • major_version class文件的主版本号

2. 常量池

  • UTF8
    存储class文件中使用到的所有字符串信息,包括用户使用到的字符串常量、类、方法、变量名及签名等,还包括class内部使用的一些关键字,如this等。
    • 程序内部使用的字符串,如String a=“hello,world"中的hello,world; @RequestMapping(”/api/goods/info")中的 /api/goods/info
    • 类名,如 java/lang/System
    • 类签名,如 Ljava/io/PrintStream;
    • 变量名,如 out
    • 方法名,如 append
    • 没有方法名的方法签名,如 (Ljava/lang/String;)Ljava/lang/StringBuilder;,即形参是String类型,返回值是StringBuilder的方法
    • class内部使用的一些关键字:如MethodParameters、LineNumberTable、LocalVariableTable、 StackMapTable、SourceFile、this
  • String
    字符串常量,通过一个index指向常量池中UTF8数组中的一个元素。
     #12 = String             #83           // normal url:
     #83 = Utf8               normal url:
    
  • Class
    类名,同String一样,也是通过一个index指向UTF8数组中的一个元素代表某个类。
    #52 = Class              #89           // java/io/IOException
    #89 = Utf8               java/io/IOException
    
  • NameAndType
    方法与类型的组合,即方法签信息,由两个UTF8数组的下标组成,如
    #90 = NameAndType        #119:#31      // printStackTrace:()V	
    #119 = Utf8              printStackTrace
    #31 = Utf8               ()V
    
  • MethodRef
    表示某个具体类的方法信息,包含类名、方法名、形参类型和返回值,由类常量+NameType常量组成,如
    #14 = Methodref          #3.#85        // java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
    #3 = Class               #72           // java/lang/StringBuilder
    #85 = NameAndType        #103:#117     // append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
    
  • InterfaceMethodRef
  • FieldRef
    表示某个具体的字段信息,包含类名、字段名及字段类型。由类常量+NameType常量组成,如
    #2 = Fieldref            #70.#71       // java/lang/System.out:Ljava/io/PrintStream;
    #70 = Class              #100          // java/lang/System
    #71 = NameAndType        #101:#102     // out:Ljava/io/PrintStream;
    

3. 类信息

  • access_flags
    类的访问权限。
  • this_class
    类索引,当前类的包类+类名,使用一个u2类型的索引值来表示,指向常量表类型为Constan_Class_info的类描述符,根据常量表中index值找到全限定名字符串。
  • super_class
    父类索引,基类信息,与this_class一样,同样使用u2类型类指向常量池中的Constan_Class_info类描述
  • interfaces
    接口索引集合,与常量池一样,使用一个u2表示集合的大小,后面紧跟着接口在常量池中的索引。

4. 字段信息

描述字段的详情信息,包括访问权限、字段名、描述信息在常量池中的索引、属性数量和具体的属性信息。针对泛型,descriptor_index指向的是泛型类型,不包含泛型参数信息,如List<String>类型的变量,descriptor_index指定的是常量池中的Ljava/utils/List;,而不是Ljava/utils/List<Ljava/lang/String;>;。在使用BCEL工具生成class文件时,如果让descriptor_index指定了包含泛型参数的泛型类型,则生成的变量在IDEA中会显示异常。按正常的规则指定不包含泛型参数的泛型类型,生成的变量在IDEA中显示只是List。如果想让生成的代码在IDEA中显示为List<String>需要有一个类型为Signature类型的属性(参考以下第6点),让此属性包含完成的变量类型签名即可。

class field_info {
    u2             access_flags;						//成员变量的访问权限
    u2             name_index;							//成员变量名对应的常量池下标index
    u2             descriptor_index;					//成员变量的签名信息对应的常量池下标index
    u2             attributes_count;					//成员变量的属性信息个数
    attribute_info attributes[attributes_count];		//成员变量的属性信息
}

5. 方法信息

方法信息与字段信息结构一样,包含了访问权限 、方法名、方法描述在常量池中的索引及属性个数和属性信息。方法的代码包含在一个类型为Code的属性中。

class method_info {
    u2             access_flags;						//方法的访问权限
    u2             name_index;							//方法名对应的常量池下标index
    u2             descriptor_index;					//方法签名信息对应的常量池下标index
    u2             attributes_count;					//方法的属性信息个数
    attribute_info attributes[attributes_count];		//方法的属性信息
}

6. 属性信息

属性的种类有以下Code、LineNumberTable、LocalVariableTable、StackMapTable、ConstrantValue、Exceptions、Signature、BootstrapMethod、InnerClass、Deprecated、Synthetic等。

Code

class Code_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 max_stack;					//操作数栈深度最大值
    u2 max_locals;					//局部变量所需的最大空间,单位是Solt,
    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];
}

LineNumberTable

描述Java源码行号与字节码行号之间的对应关系。

LocalVariableTable

描述栈中局部变量表中的变量与Java源码中定义的变量之间的关系。

StackMapTable

ConstantValue

Exceptions

列举方法中可能出现的异常

Signature

变量及方法的签名信息。因为Java语言的泛型采用擦除法实现的伪泛型,在字节码中,泛型信息在编译之后通通被擦除,如运行期做反射时无法获取泛型信息,Signature属性就是为了弥补这个缺陷而增设,可以让Java反射API能够获取反射类型。

BootstrapMethod

class BootstrapMethods_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 num_bootstrap_methods;
    {   u2 bootstrap_method_ref;
        u2 num_bootstrap_arguments;
        u2 bootstrap_arguments[num_bootstrap_arguments];
    } bootstrap_methods[num_bootstrap_methods];
}

JDK1.7版本加入到Class文件规范,表示复杂可变属性。位于类文件的属性表,用于记录invokeddynamic指定引用的引导方法限定符,最多只能有一个BootstrapMethod属性。如Lambda方法编译成class文件时,会生成一个lambda方法和一个lambda类,同时会生成一个BootstrapMethod属性。调用lambda方法时使用的即是invokedynamic

调用指令如下:

38: invokedynamic #12,  0             // InvokeDynamic #0:onDone:(Lcom/example/demo/CategoryPresenter;)Lcom/ymt/library/promise/DoneCallback;
47: invokedynamic #14,  0             // InvokeDynamic #1:onFail:(Lcom/example/demo/CategoryPresenter;)Lcom/example/library/promise/FailCallback;

常量池如下:
InvokeDynamic常量类型的第一个参数为BootstramMethod属性的index值。

#12 = InvokeDynamic      #0:#68        // #0:onDone:(Lcom/example/demo/CategoryPresenter;)Lcom/example/library/promise/DoneCallback;
#14 = InvokeDynamic      #1:#73        // #1:onFail:(Lcom/example/demo/CategoryPresenter;)Lcom/example/library/promise/FailCallback;

BootstrapMethod属性如下:

BootstrapMethods:
  0: #64 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #65 (Ljava/lang/Object;)V
      #66 invokespecial com/example/demo/CategoryPresenter.lambda$init$0:(Ljava/util/List;)V
      #67 (Ljava/util/List;)V
  1: #64 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #65 (Ljava/lang/Object;)V
      #71 invokevirtual com/example/demo/base/BasePresenter.defaultError:(Lcom/example/library/net/bean/Error;)V
      #72 (Lcom/example/library/net/bean/Error;)V

InnerClass

用于记录内部类与宿主类之间的关联关系。

Deprecated

Synthetic

工具

1. Javassit

面向Java代码的class文件生成器,不需要了解JVM相关指令。对于使用者来说相对比较友好,但它执行效率低,另外它需要全量的代码参与编译,如果要编译的class文件依赖的其他class文件不存在的话,会报错,导致编译失败。

2. ASM

asm工具直接操作class文件的字节码,需要了解JVM相关的指令。但它执行效率高,对于不会写的指令,可通过IDEA插件ASM ByteCode Viewer查看相关代码的ASM书写方式。操作时不用关心常量池问题。推荐使用此工具进行class文件的编辑工作。

3. BCEL

直接操作字节码,可获取常量池、方法、字段等信息,对于class文件分析场景,使用此工具比较方便。生成代码时,需要关注常量池问题。

应用场景

1. 分析组件之间的调用关系

典型应用如根据class文件分析方法的调用关系,对代码修改做影响范围评估;分析组件之间的依赖关系等。

实施流程

主要是对类构造中的常量池进行分析,找到方法、类之间的关联关系。

2. 修改class文件内容

典型应用如根据注解信息生成相关代码减少运行时反射调用;Jacoco对代码插桩添加探针统计代码覆盖率;美团Robust在方法入口注入代码,实现热修复能力等。

实施流程

可使用javap查看class文件的常量池、指令等信息。先使用javap对class文件进行反编译,根据class文件的信息,使用工具库进行编码,生成相关代码。

  1. 考虑好要生成什么样的代码
  2. 编写相关的Java代码,并编译成class文件
  3. 使用javap工具查看字JVM指令等信息
  4. 根据JVM指令信息使用相关工具进行编码
  5. 再次使用javap工具反编译生成的class文件
  6. 对比编译器生成的class文件与编译器生成的class文件之间的差异。也可将class文件拖到IDEA中,快速查看它们之间的差异。

参考:

  1. https://zhuanlan.zhihu.com/p/25823310
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值