1. Class文件数据类型
根据Java虚拟机规范规定,Class文件格式采用一种类似于C语言结构体的伪结构来存储数据,这种伪结构只有两种数据类型:无符号数和表。
无符号数
无符号数属于基本的数据类型,可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串值。
标记 | 含义 |
---|---|
u1 | 1个字节无符号数 |
u2 | 2个字节无符号数 |
u4 | 4个字节无符号数 |
u8 | 8个字节无符号数 |
表
表是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性地以“_info”结尾。表用于描述有层次关系的复合结构的数据,整个Class文件本质上就是一张表。
2. Class文件结构
每一个Class文件对应于一个如下所示的ClassFile结构体。
ClassFile {
u4 magic;
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];
}
2.1. 魔数
每个Class文件的头4个字节称为魔数(Magic Number),它的唯一作用是确定这个文件是否为一个能被虚拟机接受的Class文件。
2.2. 版本号
紧接着魔数的4个字节存储的是Class文件的版本号:
第5和第6个字节是次版本号(Minor Version)
第7和第8个字节是主版本号(Major Version)
2.3. 常量池(cp_info)
常量池中包含了与文件中类和接口相关的常量。
常量池中主要存放两大类常量:字面量(Literal)和 符号引用(Symbolic References)。
2.3.1. 字面量
文本字符串、声明为final的常量值等。
2.3.2. 符号引用
符号引用属于编译原理方面的改了,包括下面三类常量:
- 类和接口的全限定名(Fully Qualified Name)
- 字段的名称和描述符(Descriptor)
- 方法的名称和描述符
全限定名
当常量池入口指向类或接口时,它们给出该类或者接口的全限定名。在class文件中,全限定名的点用斜线取代了。例如,在class文件中,java.lang.Object的全限定名表示为java/lang/Object; 在class文件中,java.util.Hashtable的全限定名表示为java/util/Hashtable。
简单名称
字段名和方法名以简单名称(非全限定名)形式出现在常量池入口。例如,一个指向类java.lang.Object所属方法String toString()的常量池入口有一个形如"toString"的方法名。一个指向类java.lang.System所属字段java.io.PrintStream out的常量池入口有一个形如“out”的字段名。
描述符
字段的描述符给出了字段的类型;方法描述符给出了方法的返回值和方法参数的数量、类型以及顺序。
基本类型字符解释表
字符 | 类型 | 含义 |
---|---|---|
B | byte | 有符号字节型数 |
C | char | Unicode字符,UTF-16编码 |
D | double | 双精度浮点数 |
F | float | 单精度浮点数 |
I | int | 整数型 |
J | long | 长整数 |
S | short | 有符号短整数 |
Z | boolean | 布尔值 true/false |
L Classname; | reference | 一个名为<Classname>的实例 |
[ | reference | 一个一维数组 |
字段描述符示例
描述符 | 字段声明 |
---|---|
I | int i; |
[[J | long[][] windingRoad; |
[Ljava/lang/Object; | java.lang.Object[] stuff; |
Ljava/util/Hashtable; | java.util.Hashtable ht; |
[[[Z | boolean[][][] isReady; |
方法描述符示例
描述符 | 方法声明 |
---|---|
( ) I | int getSize(); |
( ) Ljava/lang/String; | String toString(); |
( [Ljava/lang/String;) V | void main (String[] args); |
( ) V | void wait() |
(JI) V | void wait (long timeout, int nanos) |
(ZILjava/lang/String;II) Z | boolean regionMatchs(boolean ignoreCase, int toOffset, String other, int offset, int len); |
([BII) I) | int read (byte[] b, int off, int len); |
2.3.3. 常量池的项目类型
常量池包括以下项目:
类型 | 标志 | 描述 |
---|---|---|
CONSTANT_Utf8_info | 1 | UTF-8编码的字符串 |
CONSTANT_Integer_info | 3 | 整型字面量 |
CONSTANT_Float_info | 4 | 浮点型字面量 |
CONSTANT_Long_info | 5 | 长整型字面量 |
CONSTANT_Double_info | 6 | 双精度浮点型字面量 |
CONSTANT_Class_info | 7 | 类或接口的符号引用 |
CONSTANT_String_info | 8 | 字符串类型字面量 |
CONSTANT_Fieldref_info | 9 | 字段的符号引用 |
CONSTANT_Methodref_info | 10 | 类中方法的符号引用 |
CONSTANT_InterfaceMethodref_info | 11 | 接口中方法的符号引用 |
CONSTANT_NameAndType_info | 12 | 字段或方法的部分符号引用 |
CONSTANT_MethodHandle_info | 15 | 表示方法句柄 |
CONSTANT_MethodType_info | 16 | 标识方法类型 |
CONSTANT_InvokeDynamic_info | 18 | 表示一个动态方法调用点 |
2.3.3.1. CONSTANT_Utf8_info
用于表示字符串常量的值,结构如下:
CONSTANT_Utf8_info {
u1 tag;// 值为1
u2 length;// bytes[]数组的长度
u1 bytes[length];
}
2.3.3.2. CONSTANT_Integer_info
CONSTANT_Integer_info {
u1 tag;// 值为3
u4 bytes;// 按照高位在前存储的int值
}
2.3.3.3 CONSTANT_Float_info
CONSTANT_Float_info {
u1 tag;// 值为4
u2 bytes;// 按照高位在前存储的float值
}
2.3.3.4. CONSTANT_Long_info
CONSTANT_Long_info {
u1 tag;// 值为5
u8 bytes;// 按照高位在前存储的long值
}
2.3.3.5. CONSTANT_Double_info
CONSTANT_Double_info {
u1 tag;// 值为6
u8 bytes;// 按照高位在前存储的double值
}
2.3.3.6. CONSTANT_Class_info
用于表示类或接口,结构如下:
CONSTANT_Class_info {
u1 tag;// 值为7
u2 name_index;// 包含类或者接口全限定名的CONSTANT_Utf8_info表的索引
}
2.3.3.7. CONSTANT_String_info
用于表示java.lang.String类型的常量对象,结构如下:
CONSTANT_String_info {
u1 tag;// 值为8
u2 string_index;
}
2.3.3.8. CONSTANT_Fieldref_info
CONSTANT_Fieldref_info {
u1 tag;// 值为9
u2 class_index;
u2 name_and_type_index;
}
2.3.3.9. CONSTANT_Methodref_info
CONSTANT_Methodref_info {
u1 tag;// 值为10
u2 class_index;
u2 name_and_type_index;
}
2.3.3.10. CONSTANT_InterfaceMethodref_info
CONSTANT_InterfaceMethodref_info {
u1 tag;// 值为11
u2 class_index;
u2 name_and_type_index;
}
2.3.3.11. CONSTANT_NameAndType_info
用于表示字段或方法,结构如下:
CONSTANT_NameAndType_info {
u1 tag;//值为12
u2 name_index;
u2 descriptor_index;
}
2.3.3.12. CONSTANT_MethodHandle_info
用于表示方法句柄,结构如下:
CONSTANT_MethodHandle_info {
u1 tag;// 值为15
u1 reference_kind;// 值必须在1至9之间(包括1和9),它决定了方法句柄的类型。方法句柄类型的值表示方法句柄的字节码行为
u2 reference_index;
}
2.3.3.13. CONSTANT_MethodType_info
用于表示方法类型,结构如下:
CONSTANT_MethodType_info {
u1 tag;//值为16
u2 descriptor_index;
}
2.3.3.14. CONSTANT_InvokeDynamic_info
用于表示invokedynamic指令所使用到的引导方法(Bootstrap Method)、引导方法使用到动态调用名称(Dynamic Invocation Name)、参数和请求返回类型、以及可以选择性的附加被称为静态参数(Static Arguments)的常量序列。
CONSTANT_InvokeDynamic_info {
u1 tag;//值为18
u2 bootstrap_method_attr_index;
u2 name_and_type_index;
}
2.4. 访问标志
在常量池结束之后,紧接着的两个字节代表访问标志(access_flags),这个标志用于识别一些类或者接口层次的访问信息,包括:这个Class是类还是接口;是否定义为public类型;是否定义为abstract类型;如果是类的话,是否被声明为final等。
标志名称 | 标志值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x0001 | 是否为public类型 |
ACC_FINAL | 0x0010 | 是否被声明为final,只有类可设置 |
ACC_SUPER | 0x0020 | 是否允许使用invokespecial字节码指令的新语意,invokespecial指令的语意在JDK1.0.2发生过改变,为了区别这条指令使用哪种语意,JDK1.0.2之后编译出来的类的这个标志都必须为真 |
ACC_INTERFACE | 0x0200 | 标识这是一个接口 |
ACC_ABSTRACT | 0x0400 | 是否为abstract类型,对于接口或者抽象类来说,此标志值为真,其他类值为假 |
ACC_SYNTHETIC | 0x1000 | 标识这个类并非由用户代码产生的 |
ACC_ANNOTATION | 0x2000 | 标识这是一个注解 |
ACC_ENUM | 0x4000 | 标识这是一个枚举 |
2.5. 类索引、父类索引与接口索引集合
类索引、父类索引与接口索引集合都顺序排列在访问标志之后。
类索引(this_class)和父类索引(super_class)都是一个u2类型的数据,而接口索引集合(interfaces)是一组u2类型的数据的集合,Class文件由这三项数据来确定这个类的继承关系。
2.6. 字段
字段表(field_info)用于描述接口或者类中声明的变量。
字段(field)包括类级变量以及实例变量,但不包括方法内部声明的局部变量。
field_info结构格式如下:
field_info {
u2 access_flags;
u2 name_index;// 简单名称索引
u2 descriptor_index;//描述符索引
u2 attribute_count;// 当前字段附加属性的数量
attribute_info attributes[attributes_count];
}
attributes项是由多个attribute_info表组成的列表。attributes_count指出列表中attribute_info表的数量。一个字段在其列表中可以有任意数量的属性。由Java虚拟机规范定义的三种可能会出现在此项中的属性是:ConstantValue、Deprecated和Synthetic。Java虚拟机唯一需要识别的属性是ConstantValue属性。虚拟机实现必须忽略任何无法识别的属性。
2.7. 方法
所有的方法(Method),包括实例初始化方法和类初始化方法在内,都由method_info结构所定义。在一个Class文件中,不会有两个方法同时具有相同的方法名和描述符。
method_info结构格式如下:
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
有可能在class文件中出现的两种编译器产生的方法是:实例初始化方法(名为<init>)和类与接口初始化方法(名为<clinit>)。
在此项中可能会出现的由Java虚拟机规范定义的四种属性是:Code、Deprecated、Exceptions和Synthetic。Java虚拟机只需要识别Code和Exception属性。虚拟机实现必须忽略任何无法识别的属性。
2.8. 属性
属性(Attributes)在Class文件格式中的ClassFile结构、field_info结构、method_info结构和Code_attribute结构都有使用,所有属性的通用格式如下:
attribute_info {
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];// 属性数据
}
由规范定义的attribute_info表的类型:
名称 | 使用者 | 描述 |
---|---|---|
Code | method_info | 方法的字节码和其他数据 |
ConstantValue | field_info | final变量的值 |
Deprecated | field_info、method_info | 字段或者方法被禁用的指示符 |
Exceptions | method_info | 方法可能抛出的可被检测的异常 |
InnerClasses | ClassFile | 内部、外部类的列表 |
LineNumberTable | Code_attribute | 方法的行号与字节码的映射 |
LocalVariableTable | Code_attribute | 方法的局部变量的描述 |
SourceFile | ClassFile | 源文件名 |
Synthetic | field_info、method_info | 编译器产生的字段或者方法的指示符 |
2.8.1. Code属性
Code属性是一个变长属性,位于method_info结构的属性表。定义了方法的字节码序列和其他信息。
一个Code属性只为唯一一个方法、实例类初始化方法或类初始化方法保存Java虚拟机指令及相关辅助信息。所有Java虚拟机实现都必须能够识别Code属性。如果方法被声明为native或者abstract类型,那么对应的method_info结构不能有明确的Code属性,其它情况下,method_info必须有明确的Code属性。
Code属性的格式如下:
Code_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 max_stack;// 当前方法的操作数栈在运行执行的任何时间点的最大深度
u2 max_locals;// 分配在当前方法引用的局部变量表中的局部变量的个数,包括调用此方法时用于传递参数的局部变量
u4 code_length;
u1 code[code_length];//当前方法的Java虚拟机字节码
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.8.2. ConstantValue属性
ConstantValue属性是定长属性,位于field_info结构的属性表中。ConstantValue属性表示一个常量字段的值。在一个field_info结构的属性表中最多只能有一个ConstantValue属性。
ConstantValue属性的格式如下:
ConstantValue_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 constantvalue_index;
}
2.8.3. Deprecated属性
Deprecated属性是可选定长属性。
Deprecated_attribute {
u2 attribute_name_index;
u4 attribute_length;//固定为0
}
2.8.4. Exceptions属性
Exceptions属性是一个变长属性,它位于method_info结构的属性表中。Exceptions属性指出了一个方法需要检查的可能抛出的异常。一个method_info结构中最多只能有一个Exceptions属性。
Exceptions属性格式如下:
Exceptions_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 number_of_exceptions;
u2 exception_index_table[number_of_exceptions];
}
2.8.5. InnerClasses属性
InnerClasses属性是一个变长属性,位于ClassFile结构的属性表。
InnerClasses属性是在JDK1.1中为了支持内部类和内部接口而引入的。
InnerClasses_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 number_of_classes;
{
u2 inner_class_info_index;
u2 outer_class_info_index;
u2 inner_name_index;
u2 inner_class_access_flags;
}
} classes[number_of_classes];
2.8.6. LineNumberTable属性
LineNumberTable属性是可选变长属性,位于Code结构的属性表。它被调试器用于确定源文件中行号表示的内容在Java虚拟机的code[]数组中对应的部分。
LineNumberTable_attribute属性格式如下:
LineNumberTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 line_number_table_length;
{
u2 start_pc;
u2 line_number;
} line_number_table[line_number_table_length];
}
2.8.7. LocalVariableTable属性
LocalVariableTable是可选变长属性,位于Code属性的属性表中。它被调试器用于确定方法在执行过程中局部变量的信息。
LocalVariableTable属性格式如下:
LocalVariableTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 local_variable_table_length;
{
u2 start_pc;
u2 length;
u2 name_index;
u2 descriptor_index;
u2 index;
} local_variable_table[local_variable_table_length];
}
2.8.8. SourceFile属性
SourceFile属性是可选定长字段,位于ClassFile结构的属性表。一个ClassFile结构中的属性表最多只能包含一个SourceFile属性。
SourceFile属性格式如下:
SourceFile_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 sourcefile_index;
}
2.8.8. Synthetic属性
Synthetic属性是定长属性,位于ClassFile中的属性表。如果一个类成员没有在源文件中出现,则必须标记带有Synthetic属性,或者设置ACC_SYNTHETIC标志。
Synthetic_attribute属性的格式如下:
Synthetic_attribute {
u2 attribute_name_index;
u4 attribute_length;
}
参考:
《深入理解Java虚拟机_JVM高级特性与最佳实践 第2版》
《深入Java虚拟机》
《Java虚拟机规范(Java SE 7)》