类文件结构
无关性的基石
各种不同平台的虚拟机与所有平台都统一使用的程序存储格式——字节码 (ByteCode)是构成平台无关性的基石.实现语言无关性的基础仍然是虚拟机和字节码存储格式.
Class类文件的结构
Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑 地排列在Class文件之中,中间没有添加任何分隔符,这使得整个Class文件中存储的内容 几乎全部是程序运行的必要数据,没有空隙存在。当遇到需要占用8位字节以上空间的数据项时,则会按照高位在前[1]的方式分割成若干个8位字节进行存储。
根据Java虚拟机规范的规定,Class文件格式采用一种类似于C语言结构体的伪结构来 存储数据,这种伪结构中只有两种数据类型:无符号数和表,后面的解析都要以这两种数 据类型为基础,所以这里要先介绍这两个概念。
无符号数属于基本的数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4 个字节和8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照 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文件的版本
每个Class文件的头4个字节称为魔数(Magic Number),它的唯一作用是确定这个文 件是否为一个能被虚拟机接受的Class文件.
紧接着魔数的4个字节存储的是Class文件的版本号:第5和第6个字节是次版本号 (Minor Version),第7和第8个字节是主版本号(Major Version)。
常量池
紧接着主次版本号之后的是常量池入口.入口u2类型的数据代表常量池的数量,从1开始.第0位用来表达特殊含义.
常量池中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。字面量比较接近于Java语言层面的常量概念,如文本字符串、声明为final 的常量值等。而符号引用则属于编译原理方面的概念,包括了下面三类常量:
- 类和接口的全限定名(Fully Qualified Name)
- 字段的名称和描述符(Descriptor)
- 方法的名称和描述符
常量池的项目类型如下图:
访问标志
在常量池结束之后,紧接着的两个字节代表访问标志(access_flags),这个标志用于 识别一些类或者接口层次的访问信息,包括:这个Class是类还是接口;是否定义为public 类型;是否定义为abstract类型;如果是类的话,是否被声明为final等。具体的标志位以及含义见下表:
标志名称 | 标志值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x0001 | 是否为public |
ACC_FINAL | 0x0010 | 是否为final,只有类可设置 |
ACC_SUPER | 0x0020 | JDK 1.0.2之后编译出来的类的这个标志为真 |
ACC_INTERFACE | 0x0200 | 标识是接口 |
ACC_ABSTRACT | 0x0400 | 是否为abstract,接口和抽象类为真 |
ACC_SYNTHETIC | 0x1000 | 标识这个类并非由用户代码产生 |
ACC_ANNOTATION | 0x2000 | 标识这是一个注解 |
ACC_ENUM | 0x4000 | 标识这是一个枚举 |
类索引、父类索引与接口索引集合
类索引(this_class)和父类索引(super_class)都是一个u2类型的数据,而接口索引 集合(interfaces)是一组u2类型的数据的集合,Class文件中由这三项数据来确定这个类 的继承关系.
类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限 定名。
类索引、父类索引和接口索引集合都按顺序排列在访问标志之后,类索引和父类索引 用两个u2类型的索引值表示,它们各自指向一个类型为CONSTANT_Class_info的类描述 符常量,通过CONSTANT_Class_info类型的常量中的索引值可以找到定义在 CONSTANT_Utf8_info类型的常量中的全限定名字符串.
字段表集合
class文件格式第11行,u2类型的fields_count一个.
字段表不像常量项那样各有各的结构,字段表的结构是统一的,如下表
类型 | 名称 | 数量 |
---|---|---|
u2 | access_flags | 1 |
u2 | name_index | 1 |
u2 | descriptor_index | 1 |
u2 | attributes_count | 1 |
attribute_info | attributes | attributes_count |
访问标识符
access_flags可以表明字段所具有的特性,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_SYNTHETIC | 0x1000 | 是否由编译器自动生成 |
ACC_ENUM | 0x4000 | 是否为enum |
简单名和描述符
name_index和descriptor_index两者都为常量池的索引。
name_index为字段的简单名索引(即不带有包名的名称)。
描述符的作用是用来描述字段的数据类型,方法的参数列表(包括数量、类型和顺序)和返回值。
基本数据类型和表示无返回值的void的描述符都用一个大写字母来表示,对象类型则用L加对象的全限定名表示,如下表
描述符 | 含义 |
---|---|
B | byte |
C | char |
D | double |
F | float |
I | int |
J | long |
S | short |
Z | boolean |
V | void |
L | 对象类型 |
方法表集合
class文件格式第13行,u2类型的methods_count一个。
方法表结构与字段表结构一致,如下表
类型 | 名称 | 数量 |
---|---|---|
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_SYNCHRONIZED | 0x0020 | 是否为synchronized |
ACC_BRIDGE | 0x0040 | 是否为编译器产生的桥接方法 |
ACC_VARARGS | 0x0080 | 是否接受不定参数 |
ACC_NATIVE | 0x0100 | 是否为native |
ACC_ABSTRACT | 0x0400 | 是否为abstract |
ACC_STRICTFP | 0x0800 | 是否为strictfp |
ACC_SYNTHETIC | 0x1000 | 是否由编译器自动生成 |
如果父类方法没有被重写,方发表中不会出现来自父类的方法,也有可能出现编译器自动添加的方法,如类构造器<clinit>
和实列构造器<init>
方法。
开始分析方法表集合:
0x0176+10~0x0176+11:00 01
该方法是public。
0x0176+12~0x0176+13:00 07
简单名索引为7,即<init>
。
0x0176+14~0x0176+5:00 08
描述符索引为8,即V()
。
0x0192+0~0x0192+1:00 01
属性个数为1。
接下来就是一个属性表。
属性表
在class文件、字段表和方法表都可以携带属性表集合,用于描述某些场景专有的信息。属性表开头都是u2类型的属性名称索引和u4类型的属性长度,紧接着就是不同属性的结构了。属性表中可能包含其他属性表。
Code属性表
类型 | 名称 | 数量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | max_stack | 1 |
u2 | max_locals | 1 |
u4 | code_length | 1 |
u1 | code | code_length |
u2 | exception_table_length | 1 |
exception_info | exception_table | exception_table_length |
u2 | attributes_count | 1 |
attribute_info | attributes | attributes_count |
class文件属性表
在class文件、字段表和方法表都可以携带属性表集合,用于描述某些场景专有的信息。属性表开头都是u2类型的属性名称索引和u4类型的属性长度,紧接着就是不同属性的结构了。属性表中可能包含其他属性表。
分析方法类似,结果如下:
类型 | 名称 | 值 | 说明 |
---|---|---|---|
u2 | attribute_name_index | 0x00 0D | SourceFile |
u4 | attribute_length | 0x00 00 00 02 | 属性长度2字节 |
u2 | sourcefile_index | 0x00 0E | Simple.java |