1、概述
Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑的排列在Class文件中,中间没有添加任何的分隔符,这使得整个Class文件中存储的内容几乎全部是程序运行的必要数据。当遇到需要占用8个字节以上空间的数据项时,则会按照高位在前的方式分隔成多个8位字节进行存储。
Class文件采用一种类似于C语言结构体的伪结构来存储数据,这种结构中只存在两种数据类型:无符号数和表。
- 无符号数:属于基本数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串值
- 表:由多个无符号数或其他表作为数据项构成的符合数据类型,所以表都习惯以_info结尾。表用于描述有层次关系的复合结构的数据。整个Class文件本质上就是一张表,它由下表所示的数据项构成:
表1、表结构数据项
2、Class文件构成
2.1 魔数与class文件版本号
- 每个Class文件的头4个字节成为魔数(Magic Number),它的唯一作用是确定这个文件是否为一个能被虚拟机接受的Class文件。Class文件的魔数值为:0xCAFEBABE。
- 紧接着魔数的4个字节存储的是Class文件的版本号:5、6两个字节是次版本号(Minor Version),7、8两个字节是主版本号(Major Version)。下表是常见的Class文件版本号;
表2、常见class文件版本号
2.2 常量池
常量池中的每一个常量都是一个表,在jdk1.7中共有14中表结构数据,这14中标开始的第一位是一个u1类型的标记位,具体含义如下表所示:
表3、常量池中表结构类型
2.2.1 常量池位置
- 紧接着主次版本之后就是常量池,常量池可以理解为Class文件之中的资源仓库,它是以Class文件结构中与其他项目关联最多的数据类型,也是占用文件空间最大的数据项目之一,同时它还是在Class文件中第一个出现的表结构的数据项目。
- 在常量池的入口会放置一项u2类型的数据用来代表常量池的容量计数值(constant_pool_count),并且这个容量计数是从1开始的。
2.2.2 常量池中的数据类型
- 字面量(Literal):主要存放一些文本字符串和声明为final的常量
- 符号引用(Symbolic References):主要包括类和接口的全限定名(Fully Qualified Name)、字段的名称和描述符(Descriptor)、方法的名称和描述符
2.2.3 各种表结构
表4、常量池中各种表结构
2.3 访问标志
在常量池结束之后,紧接着的两个字节代表着访问标志(access_flags),这个标志用于识别一些类或接口层次的访问信息,包括:这个Class是类还是接口;是否定义为public类型;是否定义为abstract类型;如果是类的话是否声明为final等。具体的标志位以及含义如下表所示:
表5、类文件访问属性
2.4 类索引、父类索引与接口索引集合
类索引、父类索引和接口索引集合都按顺序排列在访问标志之后,并且都是一个u2类型的数据项。类索引和父类索引用两个u2类型的索引值表示,它们各自指向一个类型为CONSTANT_Class_info的类描述符常量,通过CONSTANT_Class_info类型的常量中的索引值可以找到定义在CONSTANT_Utf8_info类型的常量中的全限定名字符串。类索引(this_class)和父类索引(super_class)都是一个u2类型的数据,而接口索引集合(interfaces)是一组u2类型的数据的集合,Class文件中由这三项数据来确定这个类的继承关系。
2.5 字段表集合
字段表(field_info)用于描述接口或类中声明的字段。字段(field)包括类级变量和实例级变量,但不包括在方法内部声明的局部变量。字段表的格式如下:
表6、字段表格式
字段修饰符放在access_flags项目中,它与类中的access_flags项目是非常类似的,都是一个u2的数据类型,其中可以设置的标志位和含义见下表:
表7、字段表访问修饰符
跟随access_flags标志的是两项索引值:name_index和descriptor_index。它们都是对常量池的引用,分别代表着字段的简单名称以及字段和方法的描述符。根据描述符规则,基本数据类型以及代表无返回值类型的void类型都用一个大写字母表示,而对象类型则是字符L加对象的全限定名表示,详见下表:
表8、对象类型标识符
2.6 方法表集合
Class文件存储格式中对方法的描述与对字段的描述几乎采用了完全一致的方式,方法表的字段如同字段表的一样,依次是访问标志(access_flags)、名称索引(name_index)、描述符索引(descriptor_index)、属性表集合(attributes)。方法表属性如下表所示:
表9、方法表属性
对于方法表,所有标志位及其取值参考下表:
表10、方法表标志位列表
2.7 属性表集合
在Class文件、字段表、方法表都可以携带自己的属性表集合,用于描述某些场景的专有信息。属性表中不要求各个属性表具有严格的顺序,只要不与已有属性重名即可。下表列举了一些java虚拟机预定的属性。
表11、虚拟机属性表类型
对于每个属性,它的名称需要从常量池中引用一个CONSTANT_Utf8_info类型的常量来表示,而属性值的格式则是完全自定义,只需要通过一个u4的长度属性去说明属性值所占用的位数即可。属性表的的结构应满足下表中所示结构:
表12、属性表结构
2.7.1 Code属性
java中方法体在经过编译之后最终以字节的形式存放在Code属性内。Code属性出现在方法表的属性集合之中(不包括抽象类或接口的方法),Code属性表结构如下:
表13、Code属性表结构
- attribute_name_index:一项指向CONSTANT_Utf8_info型常量的索引,常量值固定为”Code“,它代表该属性的属性名称。
- attribute_length:属性值的长度,由于属性名称索引与属性长度一共6个字节,所以属性值长度=属性表长度-6
- max_stack:代表了操作数栈(Operand Stacks)深度的最大值。在方法执行的任意时刻,操作数栈都不会超过这个最大值。虚拟机运行的时候需要根据这个值来分配栈帧(Stack Frame)中的操作栈深度
- max_locals:局部变量表所需要的存储空间。单位是Slot
- code_length:字节码长度
- code:编译后生成的字节码指令
- **exception_table:**包含4个字段(start_pc、end_pc、handler_pc、catch_type)。这些字段的含义是:当字节码在start_pc行到end_pc行之间(try的范围)出现了类型为catch_type的异常或其子类的异常(catch_type为指向一个CONSTANT_Class_info型常量的索引),则跳转到handler_pc行继续处理。
2.7.2 Exceptions属性
Exception属性是和Code属性平级的一项属性,它的结构如下表所示:
表14、Exceptions表属性结构
number_of_exceptions表示方法可能抛出number_of_exceptions中受查异常,每一种受查异常用一个exception_index_table项表示,exception_index_table是一个指向常量池中CONSTANT_Class_info型常量的索引,代表了该受查异常的类型
2.7.3 LineNumberTable属性
LineNumberTable属性用于描述java源码行号和字节码行号之间的对应关系。它并不是运行时必须的属性,但默认会生成到Class文件中,可以再javac中分别使用-g:none或-g:lines选项取消或要求生成这项信息。如果选择不生成LineNumberTable属性,当程序抛出异常时,堆栈中将不会显示出错的行号,并且在调试程序的时候,也无法按照源码行设置断点,其结构如下表所示:
表15、LineNumberTable属性表结构
line_number_table是一个数量为line_number_table_length、类型为line_number_info的集合,line_number_info表包含了start_pc和line_number两个u2类型的数据项,前者表示字节码行号,后者表示java源码行号
2.7.4 LocalVariableTable属性
LocalVariableTable属性用于描述栈帧中局部变量表中的变量与java源码中定义的变量的关系,它也不是运行时必须数据,但默认会生成在Class文件中。可以在javac时使用-g:none或-g:lines来取消或要求生成这项信息。如果不生成这个信息,当其他人引入这个方法时,所有参数名称会丢失,IDE会使用诸如arg1、arg2之类的占位符替代原有的参数名,这对程序运行没有影响,但是对代码编写带来较大不便,而且在调试期间无法根据参数名称从上下文中获取参数值。其结构如下表所示:
表16、LocalVariableTable属性表结构
其中local_variable_info 项代表了一个栈帧与源码中局部变量的关联,local_variable_info表结构如下所示:
表17、local_variable_info表结构
- start_pc:局部变量的生命周期开始的字节码偏移量
- length:局部变量在生命周四开始的字节码的作用范围覆盖长度
- name_index:指向常量池中CONSTANT_Utf8_info型常量的索引,代表局部变量的名称
- descriptor_index:指向常量池中CONSTANT_Utf8_info型常量的索引,代表局部变量的描述符
- index:这个局部变量在栈帧局部变量表中Slot的位置
2.7.5 SourceFile属性
SourceFile属性用于记录生成这个Class文件的源码名称。这个属性也是可选的,可以分别使用javac 的 -g :none或-g:source来关闭或要求生成这项信息。如果不生成这项信息,当抛出异常,堆栈中将不会显示出错代码所属的文件名。这个属性是一个定长的属性,其结构如下:
表18、SourceFile属性表结构
sourcefile_index数据项时指向常量池中CONSTANT_Utf8_info型常量的索引,常量值是源码文件名
2.7.6 ConstantValue属性
ConstantValue属性的作用是通知虚拟机自动为静态变量赋值。只有被static修饰的变量才可以使用这个属性。
2.7.7 InnerClasses属性
InnerClasses属性用于记录内部类和宿主类之间的关系。如果一个类中定义了内部类,那编译器将会为它以及它包含的内部类生成InnerClasses属性,该属性结构如下图所示:
表19、InnerClasses属性表结构
数据项number_of_classes代表需要记录多少个内部类信息,每个内部类的信息都由一个inner_classes_info表进行描述,inner_classes_info表结构如下:
表20、inner_classes_info表结构
- inner_class_info_index:指向常量池中CONSTANT_Class_info类型常量的索引,代表内部类的符号引用
- outer_class_info_index:指向常量池中CONSTANT_Class_info类型常量的索引,代表宿主类的符号引用
- inener_name_index:指向常量池中CONSTANT_Utf8_info型常量的索引,代表内部类的名称,如果是匿名内部类,那么这项值为0
- inner_class_access_flags:内部类的访问标志,它的取值范围见下表:
表21、内部类访问标志
2.7.8 Deprecated及Synthetic属性
Deprecated及Synthetic属性都属于标志类型的布尔属性,只存在有和没有的区别,没有属性值概念。Deprecated属性用于表示某个类、字段或方法,它可以通过在代码中使用@deprecated注释进行设置。Synthetic属性代表此字段或方法不是由java源码直接产生,而是由编译器自行添加的。Deprecated和Synthetic属性的结构如下:
表22、Deprecated和Synthetic属性的结构
2.7.9 StackMapTable属性
StackMapTable属性在JDK1.6发布后增加到Class文件规范中,它是一个复杂的变长属性,位于Code属性的属性表中。这个属性会在虚拟机的类加载的字节码验证阶段被新类型检查验证器(Type Checker)使用,目的在于替代以前比较消耗性能的基于数据流分析的类型推导验证器。StackMapTable属性中包含零至多个栈映射帧(Stack Map Frames),每个栈帧射帧都显式或隐式的代表了一个字节码的偏移量,用于表示执行到该字节码时局部变量表和操作数栈的验证类型。类型检查器会通过检查目标方法的局部变量和操作数栈所需要的类型来确定指令是否符合逻辑的约束。其结构如下:
表23、StackMapTable属性表结构
2.7.10 Signature属性
Signature属性在JDK1.5发布后增加到Class文件规范中,它是一个可选的定长属性,可以出现于类、属性表和方法表结构的属性表中。在任何类、接口、初始化方法或成员的泛型简明中如果包含了类型变量()或参数化类型(),则Signature属性会为它记录泛型签名信息。Signature属性的结构如下所示:
表24、Signature属性表结构
- signature_index:必须是一个对常量池的有效索引。常量池在该索引未知的项必须是CONSTANT_Utf8_info结构,表示类签名、方法类型签名或字段类型签名。 如果当前的Signature属性是类文件的属性,则表示是类签名;如果是方法表的属性则表示是方法类型签名;如果是字段表属性则说明是字段类型签名。
2.7.11 BootstrapMethods属性
BootstrapMethods属性是在JDK1.7发布后增加到CLass文件规范中,它是一个复杂的变长属性,位于类文件表中。这个属性用于保存invokedynamic指令引用的引导方法限定符。如果某个类文件的常量池中曾经出现过CONSTANT_InvokeDynamic_info类型的常量,那么这个类文件的属性表中必须存在一个明确的BootstrapMethods属性,另外,即使CONSTANT_InvokeDynamic_info类型的常量在常量池中出现过多次,最多也只能有一个BootstrapMethods属性。BootstrapMethods属性结构如下:
表25、BootstrapMethods属性结构
其中引用到的bootstrap_method结构如下:
表26bootstrap_method结构