[TOC]
CLASS文件结构以及各字段含义
文件结构
1 | ClassFile{ |
magic
魔数,唯一的作用就是确定这个文件是否为一个能被JVM所接受的class文件。魔数值固定为0xCAFEBABE,不会改变。
minor_version、major_version
副版本号和主版本号,他们共同构成了class文件的格式版本号。比如说某个class文件的主版本号为M,副版本号为m,那么该class的格式版本号就为M.m。
一个jvm只能支持特定版本范围内的主版本号(比如说[Mi,Mj]),和0到特定范围内的副版本号([0,m]),假设一个class文件的格式版本号为V,那么只有当Mi.0 <= V <= Mj.m
时,这个class文件才可以备此jvm支持接受。
constant_pool_count
常量池计数器,constant_pool_count的值的呢关于constant_pool表中的成员数加1.constant_pool表的索引只有在这个范围内才是有效的
值为0的constant_pool索引是无效的,但是其他用到常量池的数据结构可以使用索引0来表示不引用任何一个常量选项
constant_pool[]
常量池,constant_pool是一种表结构,它包含class文件结构及其子结构中引用的所有字符串常量、类和接口名、字段名和其他常量。常量池中的每一项都具有相同的格式特征——第一个字节作为类型,标记用于表示该项是哪种类型的常量,成为”tag byte”。常量池的索引范围是1到constant_pool_count - 1。
access_flags
访问标志,access_flags是一种掩码标志,用于表示某个接口或类的访问权限及其属性。access_flags的取值范围和相应含义见下表。
标记名 | 值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x0001 | 可以被包的类外访问。 |
ACC_FINAL | 0x0010 | 不允许有子类。 |
ACC_SUPER | 0x0020 | 当用到invokespecial指令时,需要特殊处理的父类方法。 |
ACC_INTERFACE | 0x0200 | 标识定义的是接口而不是类。 |
ACC_ABSTRACT | 0x0400 | 不能被实例化。 |
ACC_SYNTHETIC | 0x1000 | 标识并非Java源码生成的代码。 |
ACC_ANNOTATION | 0x2000 | 标识注解类型 |
ACC_ENUM | 0x4000 | 标识枚举类型 |
- 带有ACC_SYNTHETIC标志的类,意味着它是由编译器自己产生的而不是由程序员编写的源代码生成的。
- 带有ACC_ENUM标志的类,意味着它或它的父类被声明为枚举类型。
- 带有ACC_INTERFACE标志的类,意味着它是接口而不是类,反之是类而不是接口。如果一个Class文件被设置了ACC_INTERFACE标志,那么同时也得设置ACC_ABSTRACT标志。同时它不能再设置ACC_FINAL、ACC_SUPER 和 ACC_ENUM标志。
- 注解类型必定带有ACC_ANNOTATION标记,如果设置了ANNOTATION标记,ACC_INTERFACE也必须被同时设置。如果没有同时设置ACC_INTERFACE标记,那么这个Class文件可以具有表中的除ACC_ANNOTATION外的所有其它标记。当然ACC_FINAL和ACC_ABSTRACT这类互斥的标记除外。
- ACC_SUPER标志用于确定该Class文件里面的invokespecial指令使用的是哪一种执行语义。目前Java虚拟机的编译器都应当设置这个标志。ACC_SUPER标记是为了向后兼容旧编译器编译的Class文件而存在的,在JDK1.0.2版本以前的编译器产生的Class文件中,access_flag里面没有ACC_SUPER标志。同时,JDK1.0.2前的Java虚拟机遇到ACC_SUPER标记会自动忽略它。
- 在表中没有使用的access_flags标志位是为未来扩充而预留的,这些预留的标志为在编译器中会被设置为0, Java虚拟机实现也会自动忽略它们。
this_class
类索引,this_class的值必须是对constant_pool表中项目的一个有效索引值。constant_pool表在这个索引处的项必须为CONSTANT_Class_info类型常量,表示这个class文件所定义的类或接口。
super_class
父类索引,对于类来说,super_class的值必须为0或者是对constant_pool表中项目的一个有效索引值。如果它的值不为0,那constant_pool表在这个索引处的项必须为constant_class_info类型常量,表示这个Class文件所定义的类的直接父类。当前类的直接父类,以及它所有间接父类的access_flag中都不能带有ACC_FINAL标记。对于接口来说,它的Class文件的super_class项的值必须是对constant_pool表中项目的一个有效索引值。constant_pool表在这个索引处的项必须为代表java.lang.Object的CONSTANT_Class_info类型常量。如果Class文件的super_class的值为0,那这个Class文件只可能是定义的是java.lang.Object类,只有它是唯一没有父类的类。
interfaces_count
接口计数器,interfaces_count的值表示当前类或接口的直接父接口数量。
interfaces[]
接口表,interfaces[]数组中的每一个成员的值必须是一个对constant_pool表中项目的一个有效索引值,它的长度为interfaces_count。每个成员interfaces[i]必须为constant_class_info类型常量。在interfaces[]数组中,成员所表示的接口顺序和对应的源代码中给定的就诶口顺序(从左到右)一样,即interfaces[0]对应的是源代码中最左边的接口。
fields_count
字段计数器,fields_count的值表示当前class文件fields[]数组的成员个数。fields[]数组中每一项都是一个field_info结构的数据项,它用于该类或接口声明的类字段或者实例字段。
fields[]
字段表,fields[]数组中的每一个成员必须都是一个fields_info结构的数据项,用于表示当前类或接口中某个字段的完整描述。fields[]数组描述当前类或接口声明的所有字段,但不包括从父类或父接口继承的部分。
methods_count
方法计数器,methods_count的值表示当前class文件methods[]数组的成员个数。methods[]数组中每一项都是一个method_info结构的数据项。
methods[]
方法表,methods[]数组中的每个成员都必须是一个method_info结构的数据项,用于表示当前类或接口中某个方法的完整描述。乳沟某个method_info结构的access_flags项既没有设置ACC_NATIVE标志也没有设置ACC_ABSTRACT标志,那么它所对应的方法体就应当可以备jvm直接从当前类加载,而不需要引用其他类。method_info结构可以表示类和接口中定义的所有方法,包括实例方法、类方法、实例初始化方法和类或接口初始化方法。methods[]数组只描述当前类或接口中声明的方法,不包括从父类或父接口继承的方法。
attributes_count
属性计数器,attributes_count的值表示当前class文件attributes表中的成员个数。attributes表中每一项都是一个attribute_info结构的数据项。
attributes[]
属性表,attributes表的每个项的值必须是attribute_info结构。在class文件的规范中,class文件结构中的attributes表的项包括下列定义的属性:InnerClasses、EnclosingMethod、Synthetic、Signature、SourceFile、SourceDebugExtension、Deprecated、RuntimeVisibleAnnotations、RuntimeInvisibleAnnotations以及BootstrapMethods属性。支持不同版本class文件的虚拟机实现的属性也有所不同。
描述符
语法符号
描述符和签名都是用特定的语法符号来表示,这些语法是一组可表达如何根据不同的类型去产生可恰当描述它们的字符序列的标识集合。例如:
1 | FieldType: |
上面文字表达的意思是FileType可以表示BaseType、ObjectType、ArrayType三者之一。
当一个(*)跟随的非终止符出现在一个语法标识的右侧时,说明带有这个非终止符的语法标识将产生0或多个不同值,这些值按照顺序且无间隔的排列在非终止符后面。当一个有加号(+)跟随的非终止符出现在结构的右侧时,说明这个非终止符将产生一个或多个不同值,这些值按照顺序且无间隔的排列在非终止符后面。例如
1 | MethodDescriptor: |
上面文字表达的意思是MethodDescriptor的是左括号”(“、0或若干个连续排列的ParameterDescriptor值、右括号”)”、ReturnDescriptor值构成。
字段描述符
字段描述符(Field Descriptor),是一个表示类、实例或局部变量的语法符号,它是由语法产生的字符序列:
1 | FieldDescriptor: |
所有表示基础类型(BaseType)的字符、表示对象类型(ObjectType)中的字符”L”,表示数组类型(ArrayType)的”[“字符都是ASCII编码的字符。对象类型(ObjectType)中的Classname表示一个类或接口二进制名称的内部格式。表示数组类型的有效描述符的长度应该小于等于255。所有字符类型的解释如下表所示。
字符 | 类型 | 含义 |
---|---|---|
B | byte | 有符号字节型数 |
C | char | Unicode字符,UTF-16编码 |
D | double | 双精度浮点数 |
F | float | 单精度浮点数 |
I | int | 整型数 |
J | long | 长整数 |
S | short | 有符号短整数 |
Z | boolean | 布尔值 true/false |
L Classname; | reference | 一个名为的实例 |
[ | reference | 一个一维数组 |
例子:
描述int实例变量的描述符是”I”;java.lang.Object的实例描述符是”Ljava/lang/Object;”。注意,这是用到的是类Object的二进制名的内部形式(此处是内部形式)。double的三维数组”double d[][][];”的描述符为”[[[D”。
方法描述符
方法描述符(Method Descriptor)描述一个方法所需的参数和返回值信息:
1 | MethodDescriptor: |
参数描述符(ParameterDescriptor)描述需要传给这个方法的参数信息:
1 | ParameterDescriptor: |
返回值描述符(ReturnDescriptor)从从前方法返回的值,它是由语法产生的字符序列:
1 | ReturnDescriptor: |
其中VoidDescriptor表示当前方法无返回值,即返回类型是void。符号如下(字符V即void)
1 | VoidDescriptor: |
如果一个方法描述符是有效的,那么它对应的方法的参数列表总长度小于等于255,对于实例方法和接口方法,需要额外考虑隐式参数this。参数列表长度的计算规则如下:每个long和double类参数长度为2,其余的都为1,方法参数列表的总长度等于所有参数的长度之和。
例如,方法:
1 | Object mymethod(int i, double d, Thread t) |
的描述符为:
1 | (IDLjava/lang/Thread;)Ljava/lang/Object; |
注意:这里使用了Object和Thread的二进制名称的内部形式。
无论mymethod()是静态方法还是实例方法,它的方法描述符都是相同的。尽管实例方法除了传递自身定义的参数,还需要额外传递参数this,但是这一点不是由方法描述符来表达的。参数this的传递,但是这一点不是由方法描述符来表达的。参数this的传递,是由jvm实现在调用实例方法所使用的指令中实现的隐式传递。
常量池
jvm指令执行时不依赖与类、接口,实例或数组的运行时布局,而是依赖常量池(constant_pool)表中的符号信息。所有的常量池项都具有如下通用格式:
1 | cp_info{ |
常量池中,每个cp_info项的格式必须相同,它们都以一个表示cp_info类型的单字节”tag”项开头。后面info[]项的内容由tag的类型所决定。tag有效的类型和对应的取值在下表中列出。每个tag项必须跟随2个或更多字节,这些字节用于给定这个常量的信息,附加字节的信息格式由tag的值决定。
常量类型 | 值 |
---|---|
CONSTANT_Class | 7 |
CONSTANT_Fieldref | 9 |
CONSTANT_Methodref | 10 |
CONSTANT_InterfaceMethodref | 11 |
CONSTANT_String | 8 |
CONSTANT_Integer | 3 |
CONSTANT_Float | 4 |
CONSTANT_Long | 5 |
CONSTANT_Double | 6 |
CONSTANT_NameAndType | 12 |
CONSTANT_Utf8 | 1 |
CONSTANT_MethodHandle | 15 |
CONSTANT_MethodType | 16 |
CONSTANT_InvokeDynamic | 18 |
常见结构
CONSTANT_Class_info结构
CONSTANT_Class_info结构用于表示类和接口,格式如下:
1 | CONSTANT_Class_info{ |
CONSTANT_Class_info结构的说明:
- tag
tag项的值为CONSTANT_Class(7)
- name_index
name_index项的值是对常量池的一个有效索引。常量池在该索引处的项必须是constant_utf8_info结构,用来代表一个有效的类或接口二进制名称的内部形式。
CONSTANT_Fieldref_info, CONSTANT_Methodref_info和CONSTANT_InterfaceMethodref_info结构
字段:
1 | CONSTANT_Fieldref_info { |
方法:
1 | CONSTANT_Methodref_info { |
接口方法:
1 | CONSTANT_InterfaceMethodref_info { |
这些结构各项的说明如下:
- tag
CONSTANT_Fieldref_info结构的tag项的值为CONSTANT_Fieldref(9)。
CONSTANT_Methodref_info结构的tag项的值为CONSTANT_Methodref(10)。
CONSTANT_InterfaceMethodref_info结构的tag项的值为CONSTANT_InterfaceMethodref(11)。
- class_index
class_index项的值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Class_info结构,表示一个类或接口,当前字段或方法是这个类或接口的成员。
CONSTANT_Methodref_info结构的class_index项的类型必须是类(不能是接口)。CONSTANT_InterfaceMethodref_info结构的class_index项的类型必须是接口(不能是类)。CONSTANT_Fieldref_info结构的class_index项的类型既可以是类也可以是接口。
- name_and_type_index
name_and_type_index项的值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_NameAndType_info结构,它表示当前字段或方法的名字和描述符。
在一个CONSTANT_Fieldref_info结构中,给定的描述符必须是字段描述符。而CONSTANT_Methodref_info和CONSTANT_InterfaceMethodref_info中给定的描述符必须是方法描述符。
如果一个CONSTANT_Methodref_info结构的方法名以“<”(’\u003c’)开头,则说明这个方法名是特殊的,即这个方法是实例初始化方法,它的返回类型必须为空。
CONSTANT_String_info结构
CONSTANT_String_info用于表示java.lang.String类型的常量对象,格式如下:
1 | CONSTANT_String_info { |
CONSTANT_String_info结构各项的说明如下:
- tag
CONSTANT_String_info结构的tag项的值为CONSTANT_String(8)。
- string_index
string_index项的值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info
结构,表示一组Unicode码点序列,这组Unicode码点序列最终会被初始化为一个String对象。
CONSTANT_Integer_info和CONSTANT_Float_info结构
CONSTANT_Intrger_info和CONSTANT_Float_info结构表示4字节(int和float)的数值常量:
1 | CONSTANT_Integer_info { |
这些结构各项的说明如下:
- tag
CONSTANT_Integer_info结构的tag项的值是CONSTANT_Integer(3)。
CONSTANT_Float_info结构的tag项的值是CONSTANT_Float(4)。
- bytes
CONSTANT_Integer_info结构的bytes项表示int常量的值,按照Big-Endian的顺序存储。
CONSTANT_Float_info结构的bytes项按照IEEE 754单精度浮点格式表示float常量的值,按照Big-Endian的顺序存储。
CONSTANT_Float_info结构表示的值将按照下列方式来表示,bytes项的值首先被转换成一个int常量的bits:
- 如果bits值为0x7f800000,表示float值为正无穷。
- 如果bits值为0xff800000,表示float值为负无穷。
- 如果bits值在范围0x7f800001到0x7fffffff或者0xff800001到0xffffffff内,表示float值为NaN。
- 在其它情况下,设s、e、m,它们值根据bits和如下公式计算:
1 | int s =((bits >> 31) == 0) ? 1 : -1; |
则float的浮点值为数值表达式s·m·2e–150的计算结果。
CONSTANT_Long_info和CONSTANT_Double_info结构
CONSTANT_Long_info和CONSTANT_Double_info结构表示8字节(long和double)的数值常量:
1 | CONSTANT_Long_info { |
在Class文件的常量池中,所有的8字节的常量都占两个表成员(项)的空间。如果一个CONSTANT_Long_info或CONSTANT_Double_info结构的项在常量池中的索引为n,则常量池中下一个有效的项的索引为n+2,此时常量池中索引为n+1的项有效但必须被认为不可用。
CONSTANT_Long_info和CONSTANT_Double_info结构各项的说明如下:
- tag
CONSTANT_Long_info结构的tag项的值是CONSTANT_Long(5)。
CONSTANT_Double_info结构的tag项的值是CONSTANT_Double(6)。
- high_bytes和low_bytes
CONSTANT_Long_info结构中的无符号的high_bytes和low_bytes项用于共同表示long型常量,构造形式为((long) high_bytes << 32) + low_bytes,high_bytes和low_bytes都按照Big-Endian顺序存储。
CONSTANT_Double_info结构中的high_bytes和low_bytes共同按照IEEE 754双精度浮点格式表示double常量的值。high_bytes和low_bytes都按照Big-Endian顺序存储。
CONSTANT_Double_info结构表示的值将按照下列方式来表示,high_bytes和low_bytes首先被转换成一个long常量的bits:
- 如果bits值为0x7ff0000000000000L,表示double值为正无穷。
- 如果bits值为0xfff0000000000000L,表示double值为负无穷。
- 如果bits值在范围0x7ff0000000000001L到 0x7fffffffffffffffL或者0xfff0000000000001L到 0xffffffffffffffffL内,表示double值为NaN。
- 在其它情况下,设s、e、m,它们的值根据bits和如下公式计算:
1 | int s =((bits >> 63) == 0) ? 1 : -1; |
则double的浮点值为数学表达式s·m·2e – 1075的计算结果。
CONSTANT_NameAndType_info结构
CONSTANT_NameAndType_info结构用于表示字段或方法,但是和前面介绍的3个结构不同,CONSTANT_NameAndType_info结构没有标识出它所属的类或接口,格式如下:
1 | CONSTANT_NameAndType_info { |
CONSTANT_NameAndType_info结构各项的说明如下:
- tag
CONSTANT_NameAndType_info结构的tag项的值为CONSTANT_NameAndType(12)。
- name_index
name_index项的值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info结构,这个结构要么表示特殊的方法名,要么表示一个有效的字段或方法的非限定名(Unqualified Name)。
- descriptor_index
descriptor_index项的值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info结构,这个结构表示一个有效的字段描述符或方法描述符。
CONSTANT_Utf8_info结构
CONSTANT_Utf8_info结构用于表示字符串常量的值:
1 | CONSTANT_Utf8_info { |
CONSTANT_Utf8_info结构各项的说明如下:
- tag
CONSTANT_Utf8_info结构的tag项的值为CONSTANT_Utf8(1)。
- length
length项的值指明了bytes[]数组的长度(注意,不能等同于当前结构所表示的String对象的长度),CONSTANT_Utf8_info结构中的内容是以length属性确定长度而不是以null作为字符串的终结符。
- bytes[]
bytes[]是表示字符串值的byte数组,bytes[]数组中每个成员的byte值都不会是0,也不在0xf0至0xff范围内。
字符串常量采用改进过的UTF-8编码表示。
CONSTANT_MethodHandle_info结构
CONSTANT_MethodHandle_info结构用于表示方法句柄,结构如下:
1 | CONSTANT_MethodHandle_info { |
CONSTANT_MethodHandle_info结构各项的说明如下:
- tag
CONSTANT_MethodHandle_info结构的tag项的值为CONSTANT_MethodHandle(15)。
- reference_kind
reference_kind项的值必须在1至9之间(包括1和9),它决定了方法句柄的类型。方法句柄类型的值表示方法句柄的字节码行为(Bytecode Behavior)。
- reference_index
reference_index项的值必须是对常量池的有效索引:
- 如果reference_kind项的值为1(REF_getField)、2(REF_getStatic)、3(REF_putField)或4(REF_putStatic),那么常量池在reference_index索引处的项必须是CONSTANT_Fieldref_info结构,表示由一个字段创建的方法句柄。
- 如果reference_kind项的值是5(REF_invokeVirtual)、6(REF_invokeStatic)、7(REF_invokeSpecial)或8(REF_newInvokeSpecial),那么常量池在reference_index索引处的项必须是CONSTANT_Methodref_info结构,表示由类的方法或构造函数创建的方法句柄。
- 如果reference_kind项的值是9(REF_invokeInterface),那么常量池在reference_index索引处的项必须是CONSTANT_InterfaceMethodref_info结构,表示由接口方法创建的方法句柄。
- 如果reference_kind项的值是5(REF_invokeVirtual)、6(REF_invokeStatic)、7(REF_invokeSpecial)或9(REF_invokeInterface),那么方法句柄对应的方法不能为实例初始化()方法或类初始化方法()。
- 如果reference_kind项的值是8(REF_newInvokeSpecial),那么方法句柄对应的方法必须为实例初始化()方法。
CONSTANT_MethodType_info结构
CONSTANT_MethodType_info结构用于表示方法类型:
1 | CONSTANT_MethodType_info { |
- tag
CONSTANT_MethodType_info结构的tag项的值为CONSTANT_MethodType(16)。
- descriptor_index
descriptor_index项的值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示方法的描述符。
CONSTANT_InvokeDynamic_info结构
CONSTANT_InvokeDynamic_info用于表示invokedynamic指令所使用到的引导方法(Bootstrap Method)、引导方法使用到动态调用名称(Dynamic Invocation Name)、参数和请求返回类型、以及可以选择性的附加被称为静态参数(Static Arguments)的常量序列。
1 | CONSTANT_InvokeDynamic_info { |
CONSTANT_InvokeDynamic_info结构各项的说明如下:
- tag
CONSTANT_InvokeDynamic_info结构的tag项的值为CONSTANT_InvokeDynamic(18)。
- bootstrap_method_attr_index
bootstrap_method_attr_index项的值必须是对当前Class文件中引导方法表的bootstrap_methods[]数组的有效索引。
- name_and_type_index
name_and_type_index项的值必须是对当前常量池的有效索引,常量池在该索引处的项必须是CONSTANT_NameAndType_info结构,表示方法名和方法描述符。
字段
每个字段(Field)都由field_info结构所定义,在同一个Class文件中,不会有两个字段同时具有相同的字段名和描述符。
1 | field_info { |
field_info结构各项的说明如下:
- access_flags
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,表示字段为枚举类型。 |
字段如果带有ACC_SYNTHETIC标志,则说明这个字段不是由源码产生的,而是由编译器自动产生的。
字段如果被标有ACC_ENUM标志,这说明这个字段是一个枚举类型成员。
Class文件中的字段可以被设置多个表中的标记。不过有些标记是互斥的,一个字段最多只能设置ACC_PRIVATE, ACC_PROTECTED,和ACC_PUBLIC三个标志中的一个,也不能同时设置标志ACC_FINAL和ACC_VOLATILE。
接口中的所有字段都具有ACC_PUBLIC,ACC_STATIC和ACC_FINAL标记,也可能被设置ACC_SYNTHETIC标记,但是不能含有表中的其它符号标记了。
在表中没有出现的access_flags项的值为扩充而预留,在生成的Class文件中应被设置成0,Java虚拟机实现也应该忽略它们。
- name_index
name_index项的值必须是对常量池的一个有效索引。常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示一个有效的字段的非全限定名。
- descriptor_index
descriptor_index项的值必须是对常量池的一个有效索引。常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示一个有效的字段的描述符。
- attributes_count
attributes_count的项的值表示当前字段的附加属性的数量。
- attributes[]
attributes表的每一个成员的值必须是attribute结构,一个字段可以有任意个关联属性。
attributes表可出现的成员有:
ConstantValue, Synthetic, Signature, Deprecated, RuntimeVisibleAnnotations和RuntimeInvisibleAnnotations。
Java虚拟机实现必须正确的识别和读取field_info结构的attributes表中的ConstantValue属性。如果Java虚拟机实现支持版本号为49.0或更高的Class文件,那么它必须正确的识别和读取这些Class文件中的Signature, RuntimeVisibleAnnotations和RuntimeInvisibleAnnotations结构。
所有Java虚拟机实现都必须默认忽略field_info结构中attributes表所不可识别的成员。没有定义的属性不可影响Class文件的语义,它们只能提供附加描述信息。
方法
所有方法(Method),包括实例初始化方法和类初始化方法在内,都由method_info结构所定义。在一个Class文件中,不会有两个方法同时具有相同的方法名和描述符。
1 | method_info { |
method_info结构各项的说明如下:
- access_flags
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_SYNCHRONIZED | 0x0020 | synchronized,方法由管程同步 |
ACC_BRIDGE | 0x0040 | bridge,方法由编译器产生 |
ACC_VARARGS | 0x0080 | 表示方法带有变长参数 |
ACC_NATIVE | 0x0100 | native,方法引用非java语言的本地方法 |
ACC_ABSTRACT | 0x0400 | abstract,方法没有具体实现 |
ACC_STRICT | 0x0800 | strictfp,方法使用FP-strict浮点格式 |
ACC_SYNTHETIC | 0x1000 | 方法在源文件中不出现,由编译器产生 |
ACC_VARARGS 标志是用于说明方法在源码层的参数列表是否变长的。如果是变长的,则在编译时,方法的ACC_VARARGS 标志设置1,其余的方法ACC_VARARGS 标志设置为0。
ACC_BRIDGE 标志用于说明这个方法是由编译生成的桥接方法。
如果方法设置了ACC_SYNTHETIC[sɪn’θɛtɪk]标志,则说明这个方法是由编译器生成的并且不会在源代码中出现。
Class 文件中的方法可以设置多个表中的标志,但是有些标志是互斥的:一个方法只能设置ACC_PRIVATE,ACC_PROTECTED
和ACC_PUBLI 三个标志中的一个标志;如果一个方法被设置ACC_ABSTRACT 标志,则这个方法不能被设置ACC_FINAL,
ACC_NATIVE, ACC_PRIVATE, ACC_STATIC, ACC_STRICT 和ACC_SYNCHRONIZED标志。
所有的接口方法必须被设置ACC_ABSTRACT 和ACC_PUBLIC 标志;还可以选择设置ACC_VARARGS,ACC_BRIDGE 和ACC_SYNTHETIC 标志,但是不能再设置表中其它的标志了。
实例初始化方法只能设置ACC_PRIVATE,ACC_PROTECTED 和ACC_PUBLIC
中的一个标志;还可以设置ACC_STRICT, ACC_VARARGS 和ACC_SYNTHETIC 标志,但是不能再设置表中的其它标志了。
类初始化方法由Java 虚拟机隐式自动调用,它的access_flags 项的值除了ACC_STRICT 标志,其它的标志都将被忽略。
在表中没有出现的access_flags项值为未来扩充而预留,在生成的Class文件中应被设置成0,Java虚拟机实现应该忽略它们。
- name_index
name_index 项的值必须是对常量池的一个有效索引。常量池在该索引处的项必须是CONSTANT_Utf8_info结构,它要么表示初始化方法的名字(或),要么表示一个方法的有效的非全限定名。
- descriptor_index
descriptor_index 项的值必须是对常量池的一个有效索引。常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示一个有效的方法的描述符。注意:在未来的某个版本中可能会要求当access_flags 项的ACC_VARARGS 标志被设置时,方法描述符中的最后一个参数必须是数组类型。
- attributes_count
attributes_count 的项的值表示这个方法的附加属性的数量。
- attributes[]
attributes 表的每一个成员的值必须是attribute结构,一个方法可以有任意个与之相关的属性。属性表可出现的成员有:Code,Exceptions,Synthetic,Signature,Deprecated,untimeVisibleAnnotations,RuntimeInvisibleAnnotations,RuntimeVisibleParameterAnnotations,RuntimeInvisibleParameterAnnotations和AnnotationDefault结构。Java虚拟机实现必须正确识别和读取method_info结构中的属性表的Code和Exceptions属性。如果Java虚拟机实现支持版本为49.0或更高的Class文件,那么它必须正确识别和读取这些Class文件的Signature,RuntimeVisibleAnnotations,RuntimeInvisibleAnnotations,RuntimeVisibleParameterAnnotations,RuntimeInvisibleParameterAnnotations和AnnotationDefault属性。所有Java虚拟机实现必须默认忽略method_info结构中attributes表所不可识别的成员。没有定义的属性不可影响Class 文件的语义,它们只能提供附加描述信息。
属性
属性(Attributes)在Class文件格式中的ClassFile结构、field_info结构,method_info结构和Code_attribute结构都有使用,所有属性的通用格式如下:
1 | attribute_info { |
对于任意属性,attribute_name_index必须是对当前Class文件的常量池的有效16位无符号索引。常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示当前属性的名字。attribute_length项的值给出了跟随其后的字节的长度,这个长度不包括attribute_name_index和attribute_name_index项的6个字节。
有些属性因Class文件格式规范所需,已被预先定义好。这些属性在表中列出,同时,被列出的信息还包括它们首次出现的Class文件版本和Java SE版本号。在当前规范定义的环境中,也就是已包含这些预定义属性的Class文件中,它们的属性名称被保留,不能再被属性表中其他的自定义属性所使用。
属性名 | Java SE | Class文件 |
---|---|---|
ConstantValue | 1.0.2 | 45.3 |
Code | 1.0.2 | 45.3 |
StackMapTable | 6 | 50.0 |
Exceptions | 1.0.2 | 45.3 |
InnerClasses | 1.1 | 45.3 |
EnclosingMethod | 5.0 | 49.0 |
Synthetic | 1.1 | 45.3 |
Signature | 5.0 | 49.0 |
SourceFile | 1.0.2 | 45.3 |
SourceDebugExtension | 5.0 | 49.0 |
LineNumberTable | 1.0.2 | 45.3 |
LocalVariableTable | 1.0.2 | 45.3 |
LocalVariableTypeTable | 5.0 | 49.0 |
Deprecated | 1.1 | 45.3 |
RuntimeVisibleAnnotations | 5.0 | 49.0 |
RuntimeInvisibleAnnotations | 5.0 | 49.0 |
RuntimeVisibleParameterAnnotations | 5.0 | 49.0 |
RuntimeInvisibleParameterAnnotations | 5.0 | 49.0 |
AnnotationDefault | 5.0 | 49.0 |
BootstrapMethods | 7 | 51.0 |
Java虚拟机实现的Class文件加载器(Class File Reader)必须正确的识别和读取ConstantValue,Code和Exceptions属性;同样,Java虚拟机也必须能正确的解析它们的语义。
InnerClasses,EnclosingMethod和Synthetic属性必须被Class文件加载器正确的识别并读入,它们用于实现Java平台的类库。
如果Java虚拟机实现支持的Class文件的版本号为49.0或更高时,它的Class文件加载器必须能正确的识别并读取Class文件中的RuntimeVisibleAnnotations,RuntimeInvisibleAnnotations,RuntimeVisibleParameterAnnotations, RuntimeInvisibleParameterAnnotations和AnnotationDefault属性,它们用于实现Java平台类库。
如果Java虚拟机实现支持的Class文件的版本号为49.0或更高时,它的Class文件加载器必须正确的识别和读取Class文件中的Signature 属性。
如果Java虚拟机实现支持的Class文件的版本号为50.0或更高时,它的Class文件加载器必须正确的识别和读取StackMapTable属性。
如果Java虚拟机实现支持的Class文件的版本号为51.0或更高时,它的Class文件加载器必须正确的识别和读取BootstrapMethods属性。
对于剩余的预定义属性的使用不受限制;如果剩余的预定义属性包含虚拟机可识别的信息,Class文件加载器就可以选择使用这些信息,否则可以选择忽略它们。
ConstantValue属性
ConstantValue属性是定长属性,位于field_info结构的属性表中。
ConstantValue属性表示一个常量字段的值。在一个field_info结构的属性表中最多只能有一个ConstantValue属性。如果该字段为静态类型(即field_info结构的access_flags项设置了ACC_STATIC标志),则说明这个field_info结构表示的常量字段值将被分配为它的ConstantValue属性表示的值,这个过程也是类或接口申明的常量字段(Constant Field)初始化的一部分。这个过程发生在引用类或接口的类初始化方法执行之前。
如果field_info结构表示的非静态字段包含了ConstantValue属性,那么这个属性必须被虚拟机所忽略。所有Java虚拟机实现必须能够识别ConstantValue属性。
ConstantValue属性的格式如下:
1 | ConstantValue_attribute { |
- attribute_name_index
attribute_name_index项的值,必须是一个对常量池的有效索引。常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示字符串“ConstantValue”。
- attribute_length
ConstantValue_attribute结构的attribute_length项的值固定为2。
- constantvalue_index
constantvalue_index项的值,必须是一个对常量池的有效索引。常量池在该索引处的项给出该属性表示的常量值。常量池的项的类型表示的字段类型如表所示。
字段类型 | 项类型 |
---|---|
long | CONSTANT_Long |
float | CONSTANT_Float |
double | CONSTANT_Double |
int,short,char,byte,boolean | CONSTANT_Integer |
String | CONSTANT_String |
Code属性
Code属性是一个变长属性,位于method_info结构的属性表。一个Code属性只为唯一一个方法、实例类初始化方法或类初始化方法保存Java虚拟机指令及相关辅助信息。所有Java虚拟机实现都必须能够识别Code属性。如果方法被声明为native或者abstract类型,那么对应的method_info结构不能有明确的Code属性,其它情况下,method_info有必须有明确的Code属性。
1 | Code_attribute { |
- attribute_name_index
attribute_name_index项的值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示字符串“Code”。
- attribute_length
attribute_length项的值表示当前属性的长度,不包括开始的6个字节。
- max_stack
max_stack项的值给出了当前方法的操作数栈在运行执行的任何时间点的最大深度。
- max_locals
max_locals项的值给出了分配在当前方法引用的局部变量表中的局部变量个数,包括调用此方法时用于传递参数的局部变量。long和double型的局部变量的最大索引是max_locals-2,其它类型的局部变量的最大索引是max_locals-1.
- code_length
code_length项给出了当前方法的code[]数组的字节数,code_length的值必须大于0,即code[]数组不能为空。
- code[]
code[]数组给出了实现当前方法的Java虚拟机字节码。code[]数组以按字节寻址的方式读入机器内存,如果code[]数组的第一个字节是按以4字节边界对齐的话,那么tableswitch和lookupswitch指令中所有涉及到的32位偏移量也都是按4字节长度对齐的(关于code[]数组边界对齐对字节码的影响,请参考相关的指令描述)。
- exception_table_length
exception_table_length项的值给出了exception_table[]数组的成员个数量。
- exception_table[]
exception_table[]数组的每个成员表示code[]数组中的一个异常处理器(Exception Handler)。exception_table[]数组中,异常处理器顺序是有意义的(不能随意更改)。
exception_table[]数组包含如下4项:
start_pc和end_pc
start_pc和end_pc两项的值表明了异常处理器在code[]数组中的有效范围。start_pc必须是对当前code[]数组中某一指令的操作码的有效索引,end_pc要么是对当前code[]数组中某一指令的操作码的有效索引,要么等于code_length的值,即当前code[]数组的长度。start_pc的值必须比end_pc小。 当程序计数器在范围[start_pc, end_pc)内时,异常处理器就将生效。即设x为异常句柄的有效范围内的值,x满足:start_pc ≤ x < end_pc。 实际上,end_pc值本身不属于异常处理器的有效范围这点属于Java虚拟机历史上的一个设计缺陷:如果Java虚拟机中的一个方法的code属性的长度刚好是65535个字节,并且以一个1个字节长度的指令结束,那么这条指令将不能被异常处理器所处理。不过编译器可以通过限制任何方法、实例初始化方法或类初始化方法的code[]数组最大长度为65534,这样可以间接弥补这个BUG。
handler_pc
handler_pc项表示一个异常处理器的起点,它的值必须同时是一个对当前code[]数组中某一指令的操作码的有效索引。
catch_type
如果catch_type项的值不为0,那么它必须是对常量池的一个有效索引,常量池在该索引处的项必须是CONSTANT_Class_info结构,表示当前异常处理器指定需要捕捉的异常类型。只有当抛出的异常是指定的类或其子类的实例时,异常处理器才会被调用。 如果catch_type项的值如果为0,那么这个异常处理器将会在所有异常抛出时都被调用。这可以用于实现finally语句(“编译finally”)。
- attributes_count
attributes_count项的值给出了Code属性中attributes表的成员个数。
- attributes[]
属性表的每个成员的值必须是attribute结构。一个Code属性可以有任意数量的可选属性与之关联。
规范中定义的、可以出现在Code属性的属性表中的成员只能是LineNumberTable,LocalVariableTable,LocalVariableTypeTable和StackMapTable属性。
如果一个Java虚拟机实现支持的Class文件版本号为50.0或更高,那么它必须正确的识别和读取Code属性的属性表出现的StackMapTable属性。
Java虚拟机实现必须自动忽略Code属性的属性表数组中出现的所有它不能识别属性。规范中没有定义的属性不可影响Class文件的语义,只能提供附加描述信息。
StackMapTable属性
StackMapTable属性是一个变长属性,位于Code属性的属性表中。这个属性会在虚拟机类加载的类型阶段被使用。
StackMapTable属性包含0至多个栈映射帧(Stack Map Frames),每个栈映射帧都显式或隐式地指定了一个字节码偏移量,用于表示局部变量表和操作数栈的验证类型(Verification Types)。
类型检测器(Type Checker)会检查和处理目标方法的局部变量和操作数栈所需要的类型。一个存储单元(Location)的含义是唯一的局部变量或操作数栈项。
我们还将用到术语“栈映射帧”(Stack Map Frame)和“类型状态”(Type State)来描述如何从方法的局部变量和操作数栈的存储单元映射到验证类型(Verification Types)。当描述Class文件侧的映射时,我们通常使用的术语是“栈映射帧”,而当描述类型检查器侧的映射关系时,我们通常使用的术语是“类型状态”。
在版本号大于或等于50.0的Class文件中,如果方法的Code属性中没有附带StackMapTable属性,那就意味着它带有一个隐式的StackMap属性。这个StackMap属性的作用等同于number_of_entries值为0的StackMapTable属性。一个方法的Code属性最多只能有一个StackMapTable属性,否则将抛出ClassFormatError异常。
StackMapTable属性的格式如下:
1 | StackMapTable_attribute { |
StackMapTable结构项的说明如下:
- attribute_name_index
attribute_name_index项的值必须是对常量池的有效索引,常量池在该索引的项处必须是CONSTANT_Utf8_info结构,表示“StackMapTable”字符串。
- attribute_length
attribute_length项的值表示当前属性的长度,不包括开始的6个字节。
- number_of_entries
number_of_entries项的值给出了entries表中的成员数量。Entries表的每个成员是都是一个stack_map_frame结构的项。
- entries[]
entries表给出了当前方法所需的stack_map_frame结构。
每个stack_map_frame结构都使用一个特定的字节偏移量来表示类型状态。每个帧类型(Frame Type)都显式或隐式地标明一个offset_delta(增量偏移量)值,用于计算每个帧在运行时的实际字节码偏移量。使用时帧的字节偏移量计算方法为:前一帧的字节码偏移量(Bytecode Offset)加上offset_delta的值再加1,如果前一个帧是方法的初始帧(Initial Frame),那这时候字节码偏移量就是offset_delta。
只要保证栈映射帧有正确的存储顺序,在类型检查时我们就可以使用增量偏移量而不是实际的字节码偏移量。此外,由于对每一个帧都使用了offset_delta+1的计算方式,我们可以确保偏移量不会重复。
在Code属性的code[]数组项中,如果偏移量i的位置是某条指令的起点,同时这个Code属性包含有StackMapTable属性,它的entries项中也有一个适用于地址偏移量i的stack_map_frame结构,那我们就说这条指令拥有一个与之相对应的栈映射帧。
stack_map_frame结构的第一个字节作为类型标记(Tag),第一个字节后会跟随0或多个字节用于说明更多信息,这些信息因类型标记的不同而变化。
一个栈映射帧可以包含若干种帧类型(Frame Types):
1 | union stack_map_frame { |