1 class文件介绍
Class文件是一组以8个字节为基础单位的二进制流;
各个数据项目严格按照顺序紧凑地排列在文件之中,中间没有添加任何分隔符;
采用一种类似C语言结构体的伪结构来存储数据,只要两种数据类型:“无符号数”和“表”。
无符号数属于基本的数据类型,可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串值。
表是由多个无符号数或者其他表作为数据项构成的复合数据类型,为了便于区分,所有表的命名都习惯以“_info”结尾。整个Class文件本质上也可以视作一张表。
类型 | 名称 | 含义 | 数量 |
u4 | magic | 魔数 | 1 |
u2 | minor_version | 次版本号 | 1 |
u2 | major_version | 主版本号 | 1 |
u2 | constant_pool_count | 常量池容量计数值 | 1 |
cp_info | constant_pool | 常量池 | constant_pool_count-1 |
u2 | access_flags | 访问标志 | 1 |
u2 | this_class | 类索引 | 1 |
u2 | super_class | 父类索引 | 1 |
u2 | interfaces_count | 接口数量 | 1 |
u2 | interfaces | 接口索引集合 | interfaces_count |
u2 | fields_count | 字段数量 | 1 |
field_info | fields | 字段表集合 | fields_count |
u2 | methods_count | 方法数量 | 1 |
method_info | methods | 方法表集合 | methods_count |
u2 | attributes_count | 属性数量 | 1 |
attribute_info | attributes | 属性表集合 | attributes_count |
表 Class文件格式
2 魔数与Class文件的版本
2.1 魔数
每个Class文件的头4个字节被称为魔数,它的唯一作用是确定这个文件是否为一个能被虚拟机接受的Class文件。
图 class的魔术值cafebabe
2.2 次版本号与主版本号
第5和第6个字节是次版本号(Minor Version);
第7和第8个字节是主版本号(Major Version);
3 常量池
紧接着主、次版本号之后的是常量池入口,常量池是Class文件结构中与其他项目关联最多的数据,通常也是占用Class文件空间最大的数据项目之一。还是Class文件中第一个出现的表类型数据项目。
3.1 常量池容量计数值constant_pool_count
代表常量池数量。从1而不是0开始。
设计者将第0项空出来有特殊考虑:如果后面某些指向常量池的索引值的数据在特殊情况下需要表达“不引用任何一个常量池项目”的含义,可以把索引值设置为0来表示。
class文件结构中只有常量池的容量是从1开始。
3.2 常量池的项目类型
截止JDK13,常量池表中分别有17种不同类型的常量。这些都有一个共同特点,表结构起始位置的第一位是一个u1类型的标志位,代表着当前常量属于哪种常量类型。
常量 | 项目 | 类型 | 描述 |
CONSTANT_Utf8_info UTF-8编码的字符串 | tag | u1 | 值为1 |
length | u2 | UTF-8编码的字符串占用了字节数 | |
bytes | u1 | 长度为length的UTF-8编码的字符串 | |
CONSTANT_Integer_info 整型字面量 | tag | u1 | 值为3 |
bytes | u4 | 按照高位在前存储的int值 | |
CONSTANT_Float_info 浮点型字面量 | tag | u1 | 值为4 |
bytes | u4 | 按照高位在前存储的float值 | |
CONSTANT_Long_info 长整型字面量 | tag | u1 | 值为5 |
bytes | u8 | 按照高位在前存储的long值 | |
CONSTANT_Double_info 双精度浮点型字面量 | tag | u1 | 值为6 |
bytes | u8 | 按照高位在前存储的double值 | |
CONSTANT_Class_info 类或接口的符号引用 | tag | u1 | 值为7 |
index | u2 | 指向全限定名常量项的索引 | |
CONSTANT_String_info 字符串类型的字面量 | tag | u1 | 值为8 |
index | u2 | 指向字符串字面量的索引 | |
CONSTANT_Fieldref_info 字段的符号引用 | tag | u1 | 值为9 |
index | u2 | 指向声明字段的类或者接口描述符CONSTANT_Class_info的索引项 | |
index | u2 | 指向字段描述符CONSTANT_NameAndType_info的索引项 | |
CONSTANT_Methodref_info 类中方法的符号引用 | tag | u1 | 值为10 |
index | u2 | 指向声明方法的类描述符CONSTANT_Class_info的索引项 | |
index | u2 | 指向名称及类型描述符CONSTANT_NameAndType_info的索引项 | |
CONSTANT_InterfaceMethodref_info 接口中方法的符号引用 | tag | u1 | 值为11 |
index | u2 | 指向声明方法的类描述符CONSTANT_Class_info的索引项 | |
index | u2 | 指向名称及类型描述符CONSTANT_NameAndType_info的索引项 | |
CONSTANT_NameAndType_info 字段或方法的部分符号引用 | tag | u1 | 值为12 |
index | u2 | 指向该字段或方法名称常量项的索引 | |
index | u2 | 指向该字段或方法描述符常量项的索引 | |
CONSTANT_MethodHandle_info 方法句柄 | tag | u1 | 值为15 |
reference_kind | u2 | 值必须在1-9之间(包括1和9),它决定了方法句柄的类型。方法句柄类型的值表示方法句柄的字节码行为。 | |
reference_kind | u2 | 值必须是对常量池的有效索引 | |
CONSTANT_MethodType_info 方法类型 | tag | u1 | 值为16 |
descriptor_index | u2 | 值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示方法的描述符 | |
CONSTANT_Dynamic_info 表示一个动态计数常量 | tag | u1 | 值为17 |
bootstrap_method_attrindex | u2 | 值必须是对当前Class文件中引导方法表的bootstrap_methods[]数组的有效索引 | |
name_and_type_index | u2 | 值必须是对当前常量池的有效索引,常量池在该索引处的项必须是CONSTANT_NameAndType_info结构,表示方法名和方法描述符 | |
CONSTANT_InvokeDynamic_info 表示一个动态方法调用点 | tag | u1 | 值为18 |
bootstrap_method_attrindex | u2 | 值必须是对当前Class文件中引导方法表的bootstrap_methods[]数组的有效索引 | |
name_and_type_index | u2 | 值必须是对当前常量池的有效索引,常量池在该索引处的项必须是CONSTANT_NameAndType_info结构,表示方法名和方法描述符 | |
CONSTANT_Module_info 表示一个模块 | tag | u1 | 值为19 |
name_index | u2 | 值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示模块名字。 | |
CONSTANT_Package_info 一个模块中开放或者导出的包 | tag | u1 | 值为20 |
name_index | u2 | 值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info接口,表示包名称 |
表 常量池中的17种数据类型的结构总表
4 访问标志
在常量池结束后,紧接着的2个字节代表访问标志(access_flags),这个标志用于识别一些类或者接口层次的访问信息。
标志名称 | 标志值 | 含义 |
ACC_PUBLIC | 0x0001 | 是否为public类型 |
ACC_FINAL | 0x0010 | 是否被声明为final,只有类可设置 |
ACC_SUPER | 0x0020 | 是否允许使用invokespecial字节码指令的新语义 |
ACC_INTERFACE | 0x0200 | 标识这是一个接口 |
ACC_ABSTRACT | 0x0400 | 是否为abstract类型 |
ACC_SYNTHETIC | 0x1000 | 标识这个类并非由用户代码产生的 |
ACC_ANNOTATION | 0x2000 | 标识这是一个注解 |
ACC_ENUM | 0x4000 | 标识这是一个枚举 |
ACC_MODULE | 0x8000 | 标识这是一个模块 |
图 访问标志
5 类索引、父类索引与接口索引集合
类索引、父类索引和接口索引集合都按顺序排列在访问标志之后,类索引和父类索引用两个u2类型的索引值表示,它们各自指向一个类型为CONSTANT_Class_info的类描述符常量。
接口索引集合是一组u2类型的数据的集合。入口的第一项u2类型的数据为接口计数器,表示索引表的容量。如果该类没有实现任何接口,则该计数器值为0。
6 字段表与方法表集合
字段表用于描述接口或类中声明的变量(不包括在方法内部声明的局部变量)。
类型 | 名称 | 数量 | 类型 | 名称 | 数量 |
u2 | access_flags | 1 | u2 | attributes_count | 1 |
u2 | name_index | 1 | attribute_info | attributes | attributes_count |
u2 | descriptor_index | 1 |
表 字段表与方法表结构
6.1 描述符
描述符的作用是用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。
标识字符 | 含义 | 标识字符 | 含义 |
B | byte | J | long |
C | char | S | short |
D | double | Z | boolean |
F | float | V | void |
I | int | L | 对象类型,如Ljava/lang/Object |
表 描述符标识字符含义
基本数据类型以及代表无返回值的void类型都用一个大写字符来表示,而对象类型则用字符L加对象的全限定名来表示。
6.1.1 描述数组
对于数组类型,每一维度将使用一个前置的“[”字符来描述,如一个定义为java.lang.String[][]类型的二维数组将被记录成“[[Ljava/lang/String;”
6.1.2 描述方法
按照先参数列表、后返回值的顺序描述。参数列表按照参数的严格顺序放在一组小括号“()”之内。 方法 int indexOf(char[] source,int sourceOffset)的描述符为([CI)I。
7 属性表集合
属性表集合的限制稍微宽松一些,不再要求各个属性表具有严格顺序。
属性名称 | 含义 | 使用位置 |
Code | Java代码编译成的字节码指令 | 方法表 |
ConstantValue | 由final关键字定义的常量值 | 字段表 |
Deprecated | 被声明为deprecated的方法和字段 | 类、方法表、字段表 |
Exceptions | 方法抛出的异常列表 | 方法表 |
EnclosingMethod | 仅当一个类为局部类或者匿名类时才能拥有这个属性,这个属性用于标示这个类所在的外围方法 | 类文件 |
InnerClasses | 内部类列表 | 类文件 |
LineNumberTable | Java源码的行号与字节码指令的对应关系 | Code属性 |
LocalVariableTable | 方法的局部变量描述 | Code属性 |
StackMapTable | JDK6中新增的属性,供新的类型检查验证器(Type Checker)检查和处理目标方法的局部变量和操作数栈所需的类型是否匹配 | Code属性 |
Signature | JDK5中新增的属性,用于支持泛型情况下的方法签名。 | 类、方法表、字段表 |
SourceFile | 记录源文件名称 | 类文件 |
SourceDebugExtension | JDK5中新增的属性,用于存储额外的调试信息 | 类文件 |
Synthetic | 标识方法或字段为编译器自动生成的 | 类、方法表、字段表 |
LocalVariableTypeTable | JDK5中新增的属性。它使用特征签名代替描述符,是为引入泛型语法之后能描述泛型参数化类型而添加 | 类 |
RuntimeVisibleAnnotations | JDK5中新增的属性,为动态注解提供支持。该属性用于指明哪些注解是运行时(实际上运行时就是进行反射调用)可见的。 | 类、方法表、字段表 |
表 部份虚拟机规范预定义的属性
对于每一个属性,它的名称都要从常量池中引用一个CONSTANT_Utf8_info类型的常量来表示,而属性值的结构则是完全自定义的,只需要通过一个u4的长度属性去说明属性所占用的位数即可。
类型 | 名称 | 数量 |
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u1 | info | attribute_length |
表 属性表结构