Class文件是一组以8位字节为基础单位的二进制流。当遇到需要占用8位字节以上空间的数据项时,则会按照【高位在前】的方式分割成若干个8位字节进行存储,按照Java虚拟机规范的规定,Class文件结构只有两种数据类型:无符号数和表
1. 无符号数
- 无符号数属于基本的数据类型,以u1、u2、u4、u8来分别代表1个字节,2个字节,4个字节,8个字节的无符号数。
- 无符号数可用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串值。
2. 表
- 由多个无符号数或者其他表作为数据项构成的复合数据类型,以“_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_flag | 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 |
2.1 魔数
- 每个Class文件的头4个字节成为魔数(Magic Number),它的唯一作用是确定这个文件是否为一个能被虚拟机接受的Class文件,为0xCAFEBABE
2.2 版本号
- 紧接着魔数的这个字节存储的是Class文件的版本号:
- 第5和第6个字节是次版本号(Minor Version)
- 第7和第8个字节是主版本号(Major Version)
- Java的版本号是从45开始的
2.3 常量池
-
占用Class文件空间最大的数据项目之一,同时也是Class文件中第一个出现的表类型数据项目;
-
在常量池的入口处存在一项u2类型的数据,代表常量池容量计数值(constant_pool_count),这个容量计数是从1开始的。第0项常量为空的作用是为了表达“不引用任何一个常量池项目”
-
常量池中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic Reference):
-
字面量比较接近Java语言的常量概念,如文本字符串、声明为final的常量值等;
-
符号引用,包括了三类常量:类和接口的全限定名、字段的名称和描述符、方法的名称和描述符。
-
-
常量池中每一项常量都是一个表,这14种表都有一共同的特点,就是表开始的第一位是一个u1类型的标志位(tag,取值见下表“标志”列),代表当前这个常量属于哪种常量类型
常量池的项目类型
类型 | 标志 | 描述 |
---|---|---|
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 | 标识一个动态方法调用点 |
常量池中14种常量项的结构总表
常量 | 项目 | 类型 | 描述 |
CONSTANT_Utf8_info | 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_Method_Handle_info | tag | u1 | 值为15 |
reference_kind | u1 | 值必须在1~9之间(包括1和9),它决定了方法句柄的类型。方法句柄类型的值表示方法句柄字节码的行为 | |
reference_index | u2 | 值必须是对常量池的有效索引 | |
CONSTANT_Method_Type_info | tag | u1 | 值为16 |
descriptor_index | u2 | 值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示方法的描述符 | |
CONSTANT_Invoke_Dynamic_info | tag | u1 | 值为18 |
bootstrap_method-attr_index | u2 | 值必须是对当前Class文件中引导方法表的bootstrap——method[]数组的有效索引 | |
name_and_type_index | u2 | 值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_NameAndType_info结构,表示方法名和方法描述符 |
- 注:分析Class文件字节码命令:javap -v 编译后Class文件名
2.4 类访问标志
- 用于识别一些类或接口层次的访问信息,包括:这个Class是类还是接口;是否定义为public类型;是否定义为abstract类型;如果是类的话,是否被声明为final等.
具体的标志位以及标志的含义见表
访问标志 | 标志值 | 含义 |
---|---|---|
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 | 标识这是一个枚举 |
2.5 类索引、父类索引与接口索引集合
-
类索引(this_class)和父类索引(super_class)都是一个u2类型的数据,而接口索引集合(interfaces)是一组u2类型的数据的集合,Class文件中由这三项数据来确定这个类的继承关系。
-
类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名。(除了java.lang.Object外,所有Java类的父类索引都不为0);接口索引集合用来描述这个类实现了哪些接口。
-
类索引、父类索引和接口索引集合都按顺序排列在访问标志后面,类索引和父类索引用两个u2类型的索引值表示,它们各自指向一个类型为CONSTANT_Class_info的类描述符常量,通过CONSTANT_Class_info类型的常量中的索引值可以找到定义在CONSTANT_Utf8_info类型的常量中的全限定名字符串。
-
接口索引集合,入口的第一项——u2类型的数据为接口计数器(interfaces_count),表示索引表的容量。如果该类没有实现任何接口,则该计数器值为0,后面的接口索引表不占用任何字节。
##2.6 字段表集合
字段表(field_info)用于描述接口或类中声明的表量。字段(field)包括类级变量以及实例级变量,但不包括在方法内部声明的局部变量。字段叫什么名字、字段被定义为什么数据类型,这些都是无法固定的,只能引用常量池中的常量来描述。
字段表结构
类型 | 名称 | 数量 | 备注 |
---|---|---|---|
u2 | access_flags | 1 | 字段访问标志 |
u2 | name_index | 1 | 字段的简单名称(对常量池的引用) |
u2 | descriptor_index | 1 | 字段和方法的描述符(对常量池的引用) |
u2 | attributes_count | 1 | |
attribute_info | attributes | attributes_count |
字段访问标志
标志名称 | 标志值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x0001 | 字段是否public |
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 | 字段是否由编译器自动产生的 |
ACC_ENMU | 0x4000 | 字段是否enmu |
描述符的作用是用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。根据描述符规则,基本数据类型(byte、char、double、float、int、long、short、boolean)以及代表无返回值的void类型都是用大写字符来表示,而对象;类型则用字符L加对象的全限定名来表示。详见下表:
描述符标识字符含义
标识字符 | 含义 | 标识字符 | 含义 | |
---|---|---|---|---|
B | 基本类型byte | J | 基本类型long | |
C | 基本类型char | S | 基本类型short | |
D | 基本类型double | Z | 基本类型boolean | |
F | 基本类型float | V | 基本类型void | |
I | 基本类型int | L | 对象类型,如Ljava/lang/Object |
对于数组类型,每一个一维度将使用一个前置的“[”字符来描述,如一个定义为 java.lang.String[][]类型的二维数组,将被记录为:[[Ljava/lang/String。
##2.7 方法表集合
方法表结构
类型 | 名称 | 数量 | 备注 |
---|---|---|---|
u2 | access_flags | 1 | 字段访问标志 |
u2 | name_index | 1 | 字段的简单名称(对常量池的引用) |
u2 | descriptor_index | 1 | 字段和方法的描述符(对常量池的引用) |
u2 | attributes_count | 1 | |
attribute_info | attributes | attributes_count |
- 因为volatile关键字和transient关键字不能修饰方法,所以在方法表的访问标志中没有了ACC_VOLATILE标志和ACC_TRANSIENT标志
- synchronized、native、strictfp和abstract关键字可以修饰方法,所以方法表的访问标志中增加了
方法访问标志
标志名称 | 标志值 | 含义 |
---|---|---|
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_STRICTFP | 0x0800 | 方法是否为strictfp |
ACC_SYNTHETIC | 0x1000 | 方法是否是由编译器自动产生的 |