Java高级——Class文件及解析
Class文件及解析
- Class文件
- 各字段解析
- magic、minor_version、major_version
- constant_pool_count
- constant_pool
- 常量含义
- 常量池解析
- 常量池项
- access_flag
- this_class
- super_class
- interfaces_count、interfaces
- fields_count、fields
- methods_count、methods
- attributes_count、attributes
- code
- LineNumberTable
- LocalVariableTable、LocalVariableTypeTable
- Exception
- SourceFile、SourceDebugExtension
- ConstantValue
- InnerClasses
- Deprecated、Synthetic
- StackMapTable
- Signature
- BootstrapMethods
- MethodParameters
- Module
- ModulePackages
- ModuleMainClass
- RuntimeVisibleAnnotations
Class文件
Java 虚拟机具有平台无关性和语言无关性,只与Class文件关联
Class文件对应类或接口的定义信息,但反之不成立(类或接口可动态生成并送入类加载器)
Class文件以8kb为单位,当大于8kb时,高位在前,包含
- 无符号数:u1、u2、u4、u8代表1-2-4-8kb,描述数字,引用等
- 表:由无符号数或表构成,以_info结尾
当需要描述多个同一类型数据时,可用前置容量计数器+若干个连续的数据项,称为集合
各字段解析
以下面程序为例,用JDK6编出class文件
public class Test {
private int m;
public int inc() {
return m + 1;
}
}
WinHex打开如下
下文将对上图逐项翻译成如下Javap 的反编译结果
Compiled from "Test.java"
public class Test extends java.lang.Object
SourceFile: "Test.java"
minor version: 0
major version: 50
Constant pool:
const #1 = class #2; // Test
const #2 = Asciz Test;
const #3 = class #4; // java/lang/Object
const #4 = Asciz java/lang/Object;
const #5 = Asciz m;
const #6 = Asciz I;
const #7 = Asciz <init>;
const #8 = Asciz ()V;
const #9 = Asciz Code;
const #10 = Method #3.#11; // java/lang/Object."<init>":()V
const #11 = NameAndType #7:#8;// "<init>":()V
const #12 = Asciz LineNumberTable;
const #13 = Asciz LocalVariableTable;
const #14 = Asciz this;
const #15 = Asciz LTest;;
const #16 = Asciz inc;
const #17 = Asciz ()I;
const #18 = Field #1.#19; // Test.m:I
const #19 = NameAndType #5:#6;// m:I
const #20 = Asciz SourceFile;
const #21 = Asciz Test.java;
{
public Test();
Code:
Stack=1, Locals=1, Args_size=1
0: aload_0
1: invokespecial #10; //Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LTest;
public int inc();
Code:
Stack=2, Locals=1, Args_size=1
0: aload_0
1: getfield #18; //Field m:I
4: iconst_1
5: iadd
6: ireturn
LineNumberTable:
line 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 7 0 this LTest;
}
magic、minor_version、major_version
Magic Number用于确定是否为Class文件,值为0xCAFEBABE
minor_version为次版本号,major_version为主版本号,如上JDK6编译的class版本号为50
次版本号在JDK1.2后已弃用(固定为0),但在JDK12后表示当前Class文件中是否使用了预览功能,若使用则为65535
constant_pool_count
常量池计数器,从1开始,第0项常量表示不引用任何一个常量池项目,如下表示有1-21共22个常量
constant_pool
常量池主要存放
- 字面量:字符串、final常量等
- 符号引用:包、类或接口的全限定名、描述符等
常量含义
常量池每一项都是一个表,第一位代表tag代表类型
常量池解析
如上常量1的tag为0x07,即CONSTANT_Class_info,结构如下
name_index表示类或接口的符号引用,值为0x0002,表示指向常量2
如上常量2的tag为0x01,即CONSTANT_Utf8_info,结构如下
length表示字符长度,bytes表示UTF-8缩略码
- ‘\u0001’ 到 ‘\u007f’(即1-127的ASCII码)用一个字节表示
- ‘\u0080’ 到 ‘\u07ff’ 用两个字节表示
- ‘\u0800’ 到 ‘\uffff’ 用三个字节表示
上图中length为0x0004,即后续4字节为常量内容,翻译成ASCII为类名Test
- 方法、字段名都是用CONSTANT_Utf8_info描述,其最大长度为u2,即65535,超过会无法编译
常量池项
如下为截取的全部常量池项
const #1 = class #2; // Test
const #2 = Asciz Test;
const #3 = class #4; // java/lang/Object
const #4 = Asciz java/lang/Object;
const #5 = Asciz m;
const #6 = Asciz I;
const #7 = Asciz <init>;
const #8 = Asciz ()V;
const #9 = Asciz Code;
const #10 = Method #3.#11; // java/lang/Object."<init>":()V
const #11 = NameAndType #7:#8;// "<init>":()V
const #12 = Asciz LineNumberTable;
const #13 = Asciz LocalVariableTable;
const #14 = Asciz this;
const #15 = Asciz LTest;;
const #16 = Asciz inc;
const #17 = Asciz ()I;
const #18 = Field #1.#19; // Test.m:I
const #19 = NameAndType #5:#6;// m:I
const #20 = Asciz SourceFile;
const #21 = Asciz Test.java;
对应图中如下选中部分
access_flag
描述类或接口的访问信息,选项如表
如Test类的标志值为ACC_PUBLIC和ACC_SUPER,即0x0001 | 0x0020 = 0x0021
this_class
描述类的全限定名,引用常量池项,如下指向常量1,而1指向2,即类名Test
super_class
描述类所继承父类的全限定名,引用常量池项,除Object外,所有java类的super_class都不为0,如下指向常量3,而3指向4,即java/lang/Object
interfaces_count、interfaces
描述类所实现接口的全限定名,引用常量池项,若没实现任何接口则为0,后面不再占用字节
fields_count、fields
描述接口或类中声明的变量,包括类变量和实例变量,不包括方法内的局部变量,结构如下
access_flags为修饰符,选项如下
- ACC_PUBLIC、ACC_PRIVATE、ACC_PROTECTED三选一
- ACC_FINAL、ACC_VOLATILE冲突
- 接口中包含ACC_PUBLIC、ACC_STATIC、ACC_FINAL
name_index为名称,引用常量池项,即变量名
descriptor_index为描述符,引用常量池项,用于描述字段的数据类型、方法的参数列表和返回值,选项如下
-
数组用 [ 表示,如String[][] 记录为[[Ljava/lang/String
-
描述方法时先参数列表后返回值并放在括号内,如int indexOf(char[] arr)描述为([C)I
attributes_count和attributes描述额外信息,引用常量池项,若字段赋值,则会多一项Contstant Value的属性
- fields_count = 0x0001,只有一个变量
- access_flags = 0x0002,即private
- name_index = 0x0005,指向常量5=m
- descriptor_index = 0x0006,指向常量6=I,int
- attributes_count = 0x0000,无attributes
此外在fileds中
- 不会列出从父类或父接口继承的字段
- 可能出现原代码中不存在的字段,如内部类会添加指向外部类实例的字段
- java中字段需使用不同名字,但class中只要字段描述符不同,字段即可重名
methods_count、methods
描述类或接口中的方法定义(方法体代码编译成字节码指令存放在attributes中的Code属性),结构如下
access_flags为修饰符,方法无volatile、transient,新增synchronized、native、strictfp、abstract,选项如下
methods_count = 0x0002,存在构造方法<init>和源码中的inc()
下面只介绍<init>
- access_flags = 0x0001,构造方法为public
- name_index = 0x0007,指向常量7=<init>
- descriptor_index = 0x0008,指向常量8=()V,无参数返回void
- attributes_count = 0x0001,存在一个attributes_info
- 后面内容为Code attribute,在下面讲解
此外,对于methods
- 不会列出子类未复写的父类方法
- 可能出现原代码中不存在的方法,如<clinit>()和<init>()
- Java中方法签名包括方法名称、参数顺序及参数类型,而class签名还包括方法返回值以及受查异常表
attributes_count、attributes
Class、Field、Method都可以携带attributes,无严格顺序,但属性名不可重复,结构如下
attribute_length 表示属性值长度,为属性表长度减去6u(即减去attribute_name_index和attribute_length长度)
attribute_name_index代表属性名,引用常量池,选项如下
code
描述方法体代码,接口或抽象类方法无此属性,结构如下
max_stack代表了操作数栈最大深度,方法执行时不会超过此值,运行时需根据此值分配栈帧中的操作栈深度
max_locals代表了局部变量表所需的存储空间,单位为Slot
- 并非所有局部变量所占Slot之和
- Slot可复用,当超出某局部变量的作用域时,其所占Slot可被其他局部变量使用
- 编译器会根据变量的作用域来分配Slot
- 根据同时生存的最大局部变量数量和类型计算出max_locals的大小
code_length代表字节码指令长度,实际只用了u2,超过65535条字节码指令会编译失败
code是字节码指令,其取值在此
exception_table_length表示异常处理表长度
exception_table 为异常处理表,结构如下
- 若[start_pc,end_pc)行出现了catch_type或其子类异常,则转到第handler_pc行处理
- 若catch_type=0,则任意异常都转到handler_pc行处理
如上图,刚刚说到<init>方法中attributes_count = 0x0001,存在一个attributes_info
- attribute_name_index = 0x0009,指向常量9=Code
- attribute_length = 0x0000002F,即attribute长度为2F
- max_stack = 0x0001
- code_length = 0x00000005,即接下来5字节是字节码指令
- 2A对于指令为aload_0,将第0个变量槽中reference变量推送到操作数栈顶
- B7对应指令invokespecial,调用栈顶的reference所指向对象的构造方法、private方法或父类方法,后跟一个u2的参数
- 000A指向常量10=<init>(),为invokespecial的参数
- B1对应指令return
- exception_table_length = 0x0000,即无异常表
- attributes_count = 0x0002,存在两个attributes_info
- 后面内容为LineNumberTable和LocalVariableTable attributes,在下面讲解
上图对应javap
Code:
Stack=1, Locals=1, Args_size=1
0: aload_0
1: invokespecial #10; //Method java/lang/Object."<init>":()V
4: return
Args_size = 1表示的是this
- 即this其实是作为参数传入实例方法
- 实例方法的局部变量表中至少存在this,存放在第一个Slot
- static方法无this,Args_size = 0
LineNumberTable
描述源码行号与字节码行号之间的对应关系
- 并非运行时必须,但默认生成
- 可使用-g:none或-g:lines取消生成
- 若不生成,当抛出异常时不会显示出错的行号,且调试无法设置断点
line_number_talbe_length表示对应关系的数量
line_number_info包含两个u2的start_pc和line_number,表示字节码行号和源码行号
- attribute_name_index = 0x000C,指向常量C=LineNumberTable
- attribute_length = 0x00000006,属性长度为6
- line_number_table_length = 0x0001,即一个对应关系
- start_pc = 0x0000,line_number = 0x0001,即0映射到1
对应的javap中内容为
LineNumberTable:
line 1: 0
LocalVariableTable、LocalVariableTypeTable
用于描述栈帧中局部变量表的变量与Java源码中定义的变量之间的关系,如下
- 非运行时必须,但默认生成
- 可使用-g:none或-g:vars取消生成
- 若没有生成,引用该方法时参数名会丢失变为arg0、arg1
local_variable_info代表了一个栈帧与源码中的局部变量的关联,如下
- start_pc 表示局部变量的生命周期开始的字节码偏移量
- length 表示作用范围长度
- name_index 表示名称
- descriptor_index 表示描述符
- index 表示栈帧的局部变量表中Slot的位置
JDK5后新增LocalVariableTypeTable,把descriptor_index替换成了字段的签名,用于泛型
- attribute_name_index = 0x000D,指向常量D = LocalVariableTable
- attribute_length = 0x0000000C,长度为12
- local_variable_length = 0x0001,有一个局部变量
- start_pc = 0x0000,作用域从0开始
- length = 0x0005,作用域长度为5
- name_index = 0x000E,指向常量E = this
- descriptor_index = 0x000F,指向常量F = LTest,即Test对象
- index = 0x0000,solt = 0
对应javap的内容为
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LTest;
至此,init()方法就讲解完了,后面是inc()方法不再赘述,如下
Exception
描述方法可能抛出的Checked Exception,即throws后面的声明的异常,结构如下
number_of_exceptions表示可能抛出多少种Checked Exception
exception_index_table表示Checked Exception的类型
SourceFile、SourceDebugExtension
描述生成这个Class文件的源码文件名称
- 可选,可通过-g:none或-g:source关闭生成
- 若不生成,当抛出异常时不显示出错代码所属的文件名
sourcefile_index为文件名
JDK5后新增SourceDebugExtension用于存储额外的代码调试信息
debug_extension存储额外的调试信息,一个类中最多只允许存在一个SourceDebugExtension属性
ConstantValue
通知虚拟机自动为静态变量赋值,只有static变量才能使用
- 非static变量的赋值是在<init>()方法
- 若为final static变量且数据类型为基本类型或String,用ConstantValue属性初始化
- 若非final的static变量,或并非基本类型及字符串,用<clinit>()方法初始化,因为ConstantValue只能指向基本类型或String的常量池引用
attribute_length值固定为2,即constantvalue_index的长度
constantvalue_index表示常量池项引用,即变量值
InnerClasses
描述内部类与宿主类之间的关联
number_of_classes表示内部类个数,inner_classes_info结构如下
inner_class_info_index/outer_class_info_index为内/外部类符号引用
inner_name_index 为内部类名称,若匿名内部类为0
inner_class_access_flags为内部类访问标志,选项如下
Deprecated、Synthetic
Deprecated表示某个类、字段或者方法,已不再推荐使用,通过@deprecated设置
Synthetic表示此字段或方法并不是由源码直接产生的,而是由编译器自行添加的,但不包括init和clinit
attribute_length = 0,因为无需设置值,有attribute_name_index即代表存在该属性
StackMapTable
一个Code只能有一个StackMapTable,用于类型检查验证器以代替类型推导验证器,省略了运行时的类型推导,转为在编译阶段将验证类型记录在Class
StackMapTable包含栈映射帧存储字节码偏移量,用于表示执行到该字节码时局部变量表和操作数栈的验证类型
类型检查验证器会通过检查目标方法的局部变量和操作数栈所需要的类型来确定一段字节码指令是否符合逻辑约束
Signature
描述泛型签名信息,用于反射API获取泛型类型(如果没有这个属性,泛型擦除后获取不到<T>)
signature_index表示类签名或方法类型签名或字段类型签名的引用
BootstrapMethods
描述invokedynamic指令引用的引导方法限定符
num_bootstrap_methods表示引导方法个数,bootstrap_method结构如下
bootstrap_method_ref表示引导方法
num_bootstrap_arguments表示方法参数个数
bootstrap_arguments表示参数
MethodParameters
描述方法各个形参的名称和信息,若没有这个属性,调用class文件的方法时不会有参数提示
parameter_count表示参数个数,parameter结构如下
name_index为参数名字,access_flags为参数类型
- 0x0010(ACC_FINAL):final
- 0x1000(ACC_SYNTHETIC)编译器自动生成
- 0x8000(ACC_MANDATED):源文件中隐式定义,如this
Module
描述模块名称、版本、标志等
module_name_index为模块名,module_flag选项如下
- 0x0020(ACC_OPEN):开放模块
·0x1000(ACC_SYNTHETIC):模块是编译器自动生成的
·0x8000(ACC_MANDATED):模块是在源文件中隐式定义的
module_version_index为模块版本号
requires、exports、opens、uses和provides差不多,只介绍exports
exports_index被该模块导出的包,exports_flags是该导出包的状态,选项如下
- 0x1000(ACC_SYNTHETIC):导出包是编译器自动生成
- 0x8000(ACC_MANDATED):导出包在源文件中隐式定义
exports_to_count为导出包的限定计数器
- 为零则完全开放的,其他模块可访问该包
- 不为零,则exports_to_index指定模块才被允许访问该导出包
ModulePackages
描述模块中所有的包,不论是不是被export或者open的
package_count是计数器,package_index表示当前模块中的包
ModuleMainClass
main_class_index代表了该模块的主类
RuntimeVisibleAnnotations
描述类、字段或方法的声明上记录运行时可见注解,用于反射获取注解
num_annotations是计数器,annotations是运行时可见的注解,结构如下
type_index是注解类型,element_value_pairs元素为注解参数和值的键值对
类似的还有
- RuntimeInvisibleAnnotations
- RuntimeVisibleParameterAnnotations
- RuntimeInvisibleParameterAnnotations
- RuntimeVisibleTypeAnnotations
- RuntimeInvisibleTypeAnnotations