class类文件结构
1. 简介
Class文件是一组以8字节为基础单位的二进制流,
各个数据项目严格按照顺序紧凑排列在class文件中,
中间没有任何分隔符,这使得class文件中存储的内容几乎是全部程序运行的程序。
1.1 无符号数
属于基本数据类型,主要可以用来描述数字、索引符号、数量值或者按照UTF-8编码构成的字符串值,大小使用u1、u2、u4、u8分别表示1字节、2字节、4字节和8字节。
1.2 表
是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有的表都习惯以“_info”结尾。表主要用于描述有层次关系的复合结构的数据,比如方法、字段。需要注意的是class文件是没有分隔符的,所以每个的二进制数据类型都是严格定义的。具体的顺序定义如下:
类型 | 名称 | 数量 | 备注 |
---|---|---|---|
u4 | magic | 1 | 魔数 |
u2 | minor_version | 1 | 次版本号 |
u2 | major_version | 1 | 主版本号 |
u2 | constant_pool_count | 1 | 常量池计数器 |
c_info | constant_pool | constant_pool_count -1 | 常量池表 |
u2 | access_flags | 1 | |
u2 | this_class | 1 | 类索引 |
u2 | super_class | 1 | 父类索引 |
u2 | interface_count | 1 | 接口数量 |
u2 | interfaces | interface_count | 接口索引集合 |
u2 | field_count | 1 | 字段数量 |
field_info | fields | field_count | 字段表 |
u2 | method_count | 1 | 字段数量 |
method_info | methods | method_count | 方法表 |
u2 | attributes_count | 1 | 属性计数器 |
attribute_info | attributes | attributes_count | 属性表 |
2.各部分详细介绍
2.1 魔数 magic
唯一的作用是确定这个文件是否为一个能被虚拟机所接受的class文件。值为:0xCAFEBABE
2.2 版本号 minor_version and major_version
用来标识版本号,JDK版本号只能向下兼容。
JDK版本 | class文件主版本号 |
---|---|
JDK1.1 | 45 |
JDK1.2 | 46 |
JDK1.3 | 47 |
JDK1.4 | 48 |
JDK1.5 | 49 |
JDK1.6 | 50 |
JDK1.7 | 51 |
JDK1.8 | 42 |
2.3 常量池计数器和常量池表 constant_pool_count and constant_pool
常量池主要两大类常量:字面量和符号引用。字面量如文本字符串,什么为final的常量值;符号引用包括:类和类的全限定名、字段的名称和描述符、方法的名称和描述符。
常量池的每一项都是一个表,一共11(JDK1.7之前)+3(JDK1.7) = 14 种。每个常量开始的第一个字节称为标志位(tag),表示当前常量的类型。具体如下:
类型 | 标志位 | 描述 | 项目组成 |
---|---|---|---|
CONSTANT_Utf8_info | 1 | UTF-8编码的字符串 | u2 长度 u1 对应长度的字符串 |
CONSTANT _Interger_info | 3 | 整型字面量 | u4 按高位在前存储int值 |
CONSTANT _Float_info | 4 | 浮点型字面量 | u4 按高位在前存储float值 |
CONSTANT _Long_info | 5 | 长整型字面量 | u8 按高位在前存储long值 |
CONSTANT _Double_info | 6 | 双精度浮点型面量 | u8 按高位在前存储double值 |
CONSTANT _Class_info | 7 | 类或接口的符号应用 | u2 指向全限定名常量项CONSTANT_Utf8_info的索引 |
CONSTANT_String_info | 8 | 字符串类型字面量 | u2 指向字符串字面量CONSTANT_Utf8_info的索引 |
CONSTANT_Fieldref_info | 9 | 字段的符号引用 | u2 指向字段所属类或接口CONSTANT_Class_info常量的索引) u2 指向字段描述符_NameAndType_info 的索引 |
CONSTANT_Methodref_info | 10 | 类中方法的符号引用 | u2 指向方法所属类CONSTANT_Class_info常量的索引) u2 指向名称及类型描述符_NameAndType_info 的索引 |
CONSTANT_InterfaceMethodref_info | 11 | 接口中方法的符号引用 | u2 指向方法所属接口CONSTANT _Class_info常量的索引) u2 指向名称及类型描述符_NameAndType_info 的索引 |
CONSTANT_NameAndType_info | 12 | 字段或方法的部分符号引用 | u2 指向该字段或方法名称常量项的索引 u2 指向该字段或方法描述符常量项的引用 |
CONSTANT_MethodHandle_info | 15 | 表示方法句柄 | |
CONSTANT_MethodType_info | 3 | 标识方法类型 | |
CONSTANT_InvokeDynamic_info | 3 | 表示一个动态方法调用点 |
2.4 访问标志 access_flags
u2:用来识别一些类或者接口的访问信息,主要包括以下内容:
- 这个class是类 还是接口
- 是否定义为public
- 是否定义为abstract
- 如果是类的话,是否被声明为final
标志名称 | 标志值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x0001 | 是否为public |
ACC_FINAL | 0x0010 | 是否声明为final,只有类可设置 |
ACC_SUPER | 0x0020 | 是否允许使用invokespecial字节码指令的新语义,这个指令的语义在jdk1.0.2发生过变化。为了区别,JDK1.0.2之后编译出来的类的这个标志必须为真 |
ACC_INTERFACE | 0x0200 | 标志为接口 |
ACC_ABSTRACT | 0x0400 | 是否为abtract类型,抽象类和接口为真 |
ACC_SYNTHETIC | 0x1000 | 标志这个类并非由用户代码产生 |
ACC_ANNOTATION | 0x2000 | 注解 |
ACC_ENUM | 0x4000 | 枚举 |
2.5 类索引、父类索引、接口索引集合 this_class、super_class 、interfaces
其中类索引和父类索引都是指向常量池CONSTANT_Class_info常量的索引。接口索引集合由一个u2的接口计数器和多个指向CONSTANT_Class_info的索引组成。
2.6 字段表 field_info
字段包括类变量和实例变量,不包括方法内部生命的局部变量。结构如下:
类型 | 名称 | 数量 | 描述 |
---|---|---|---|
u2 | access_flags | 1 | 访问标志 |
u2 | name_index | 1 | 字段或方法名称在常量池的索引 |
u2 | descriptor_index | 1 | 字段或方法描述符在常量池的索引 |
u2 | attributes_count | 1 | 属性计数器 |
attributes_info | attributes | attributes_count | 属性表 |
1. access_flags
字段的访问标识,具体如下:
标志名称 | 标志值 | 含义 |
---|---|---|
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_SYNTHTIC | 0x1000 | 字段是否由编译器自动产生 |
ACC_ENUM | 0x4000 | 字段是否为enum |
2. descriptor_index
字段和方法的描述符,主要用来表示字段的数据类型、方法的参数列表(数量、类型、顺序)、方法的返回值。根据描述符规则,基本数据类型(byte、short、int、long、float、double、char、boolean)以及无返回值的void都可以用一个大写字符来表示,引用数据类型用L家对象的全限定名来表示,数组类型,用[来表示,二维数组则是[[,详细如下:
标识符 | 数据类型 |
---|---|
B | byte |
C | char |
D | double |
F | float |
I | int |
J | long |
S | short |
Z | boolean |
V | void |
L | 对象类型,如Ljava/lang/object |
[ | 数组,如 int[],表示为[I |
方法参数列表加返回值标志:
- void add(int a,char[]) (I[C)V
- String m1(String a) (Ljava/lang/String)Ljava/lang/String
字段集合中不会列出从父类或者接口中继承来的字段,但有可能列出原本Java代码里面不存在的字段,譬如在内部类中为了保持对外部类的访问,会自动添加指向外部类实例的字段。且字段是无法重载的。
3. attributes
比如final static int m = 10,10的值会存在属性表里。属性表详细介绍,后续讲解
2.7 方法表 method_info
方法表的数据结构与字段表相同,见上文中字段表的数据结构。
1. access_flags
方法的访问标志与字段的访问标识有些许区别,具体如下:
标志名称 | 标志值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x0001 | 方法是否为public |
ACC_PRIVATE | 0x0002 | 方法是否为private |
ACC_PROTECTED | 0x0004 | 方法是否为protected |
ACC_STATIC | 0x0008 | 方法是否为static |
ACC_FINAL | 0x0010 | 方法是否为final |
ACC_SYNCHRONIZED | 0x0020 | 方法是否为synchronizedl |
ACC_BRIDGE | 0x0040 | 方法是否是有编译器产生的桥接方法 |
ACC_VARARGS | 0x0080 | 方法是否接受不定参数 |
ACC_NATIVE | 0x0100 | 方法是否为native |
ACC_ABSTRACT | 0x0400 | 方法是否为abstract |
ACC_STRICTFP | 0x0800 | 方法是否为strictfp |
ACC_SYNTHTIC | 0x1000 | 字段是否由编译器自动产生 |
如果子类没有重新父类方法,那个这个方法不会出现在子类的方法表中。但同样的,有可能会出现由编译器自动添加的方法,最典型的是类构造器“”和实例构造器“”
问:方法中的代码存到哪去了呢?存在方法表一个名为code的属性里面。
2.8属性表 attribute_info
属性表可以在字段表、方法表内部,也可以在最外层,与字段表平级。方法表与class文件中的其他数据结构不同,不再要求属性表有严格的顺序。属性表结构如下:
类型 | 名称 | 数量 | 描述 |
---|---|---|---|
u2 | attribute_name_index | 1 | 属性名索引 |
u4 | attribute_length | 2 | 属性长度 |
u1 | info | attribute_length | 属性内容 |
在jdk1.8中,jvm规范定义了23种属性,详情点击jvm官方文档