class文件结构
class文件是由一个个的字节码组成的。因为class文件是由一个个字节组成的,所以如果当一个数据大于一个字节的时候,是使用无符号大端模式进行存储的。类文件由单个ClassFile结构组成:
上面的结构看起来可以比较抽象,那么可以看一下下面这张示意图:
魔数(u4 magic)
魔数项提供识别字节码文件格式的幻数,它的值为0xCAFEBABE。
次版本号(u2 minor_version)
主版本号(u2 major_version)
主版本号和次版本号一起确定类文件格式的版本。如果类文件具有主版本号M和次版本号m,则我们将其类文件格式的版本表示为M.m。因此,类文件格式版本可以按字典顺序排序,例如,1.5 <2.0 <2.1。
当且仅当v位于某个连续范围Mi.0≤v≤Mj.m时,Java虚拟机可以支持版本v的类文件格式。Java虚拟机实现符合的Java SE平台的发行版级负责确定范围。
JDK 1.0.2版中的Oracle Java虚拟机实现支持45.0到45.3的类文件格式版本。JDK发布1.1.*支持类文件格式版本,范围为45.0到45.65535(含)。对于k≥2,JDK版本1.k支持45.0到44 + k.0范围内的类文件格式版本。
常量项个数(u2 constant_pool_count)
constant_pool_count项的值等于常量池中项数加1。如果常量池索引大于0且小于constant_pool_count,则认为它是有效的,但§4.4.5中注明的long和double类型的常量除外。
因为常量项个数只有两个字节表示,所以常量项是有数量限制的(最多65535个常量项)。
常量池(cp_info constant_pool[constant_pool_count-1])
常量池是一个结构表,表示各种字符串常量,类和接口名称,字段名称以及ClassFile结构及其子结构中引用的其他常量。每个常量项的格式由其第一个“标记(tag)”字节指示。
常量池索引是从1到constant_pool_count - 1。例如constant_pool_count = 0x0025 = 37,实际常量项个数是36,这样做的目的是满足后面其他结构中需要表明不引用任何一个常量项的含义,这个时候就将索引值置为0。
用专业一点的术语描述的话常量池中保存的内容就是字面量和符号引用。
字面量包括1)文本字符串,2)声明为final的常量值。
符号引用包括1)类和接口的全限定名,2)字段名称和描述符,3)方法名称和描述符。
标记(tag)有如下几种:
常见的常量项:
定义或引用的类或接口:
CONSTANT_Class_Info {
u1 tag; // CONSTANT_Class 07
u2 name_index; // 常量池有效索引,该索引处的常量项必须是CONSTANT_Utf8_info结构的,
// 表示以内部形式(二进制的类和接口名称)编码的有效二进制类或接口名称。
}
例:
#2 = Class #16 // hpl/operators/Tank
常量树:
字节码:
二进制的类和接口名称
出现在类文件结构中的类和接口名称始终以二进制的全限定名称形式表示。这些名称总是以CONSTANT_Utf8_info的结构呈现,因此可以从整个Unicode代码空间绘制,而不是进一步约束。类和接口名称是从,那些具有此类名称作为其描述符的一部分的CONSTANT_NameAndType_info结构,以及所有CONSTANT_Class_info结构,中引用的。
由于历史原因,出现在类文件结构中的二进制名称的语法与JLS§13.1中记录的二进制名称的语法不同。在此内部形式中,通常将构成二进制名称的标识符分隔的ASCII句点(.)替换为ASCII正斜杠(/)。标识符本身必须是非限定名称。
例如,Thread类的正常二进制名称是java.lang.Thread。在类文件格式的描述符中使用的内部形式中,使用表示字符串java/lang/Thread的CONSTANT_Utf8_info结构来实现对类Thread的名称的引用。
因为数组是对象,所以操作码anewarray和multianewarray可以通过常量池中的CONSTANT_Class_info结构引用数组“类”。对于此类数组类,类的名称即数组类型的描述符。例如,表示二维int类型数组:int[][],类名是 [[I。
Thread数组:Thread[] 的类名就是 [Ljava/lang/Thread; 。
仅当数组类型描述符在255个以内维度时,它才有效。
字面量(固定的字符串值):
CONSTANT_UTF8_Info {
u1 tag; // CONSTANT_Utf8 01
u2 length; // length项的值给出bytes数组中的字节数(不是结果字符串的长度)。
// CONSTANT_Utf8_info结构中的字符串不允许以空值结束。
u1 bytes[length]; // bytes数组包含了字符串中的每一个字节,
// 且不能有任何一个字节的值是(byte)0或位于范围(byte)0xf0 - (byte)0xff之间。
}
字符串内容以改进的UTF-8编码。对改进的UTF-8字符串进行编码,以便仅包含非空ASCII字符的代码符序列的每个代码符仅使用1个字节来表示,并且可以表示Unicode代码空间中的所有代码符。
注意:因为class文件中的方法名,字段名等都是要引用UTF8 Info的,但是UTF8 Info的数据长度(u2 length)就是2个字节,所以方法名,字段名的长度最大就是65535字节。
例:
#6 = Utf8 <init>
字节码:
其他,如:
#4 = Utf8 level
#5 = Utf8 I
#6 = Utf8 <init>
#7 = Utf8 ()V
#8 = Utf8 Code
#9 = Utf8 LineNumberTable
#10 = Utf8 LocalVariableTable
#11 = Utf8 this
#12 = Utf8 Lhpl/operators/Tank;
#13 = Utf8 SourceFile
#14 = Utf8 Assignment.java
#16 = Utf8 hpl/operators/Tank
#17 = Utf8 java/lang/Object
String类型的常量对象:
CONSTANT_String_INFO {
u1 tag; // CONSTANT_String 08
u2 string_index; // 常量池有效索引,该索引处的常量项必须是CONSTANT_Utf8_info结构的,
// 表示待初始化的String对象的Unicode代码符序列。
}
例:
#8 = String #58 // a =
常量树:
字节码:
类字段引用(字段、方法和接口方法有类似的常量项结构):
CONSTANT_Fieldref_Info {
u1 tag; // CONSTANT_Fieldref 09
u2 class_index; // 常量池有效索引,该索引处的常量项必须是CONSTANT_Class_info结构,
// 表示将该字段或方法作为成员的类或接口类型。
u2 name_and_type_index; // 常量池有效索引,该索引处的常量项是CONSTANT_NameAndType_info结构的,
// 表示字段或方法的名称和描述符
}
例:
#5 = Fieldref #55.#56 // java/lang/System.out:Ljava/io/PrintStream;
常量树:
字节码:
类引用字段或方法(在不给出类或接口类型时用于表示字段或方法):
CONSTANT_NameAndType_Info {
u1 tag; // CONSTANT_NameAndType 0C
u2 name_index; // 常量池有效索引,该索引处的常量必须是CONSTANT_Utf8_info结构,
// 表示特殊方法名称<init>或表示字段或方法的有效非限定名称。
u2 descriptor_index; // 常量池有效索引,
// 该索引处的常量项必须是表示有效字段描述符或方法描述符的CONSTANT_Utf8_info结构。
}
CONSTANT_NameAndType_info结构在不指示类或接口类型的情况下表示字段或方法。
例:
#15 = NameAndType #6:#7 // "<init>":()V
常量树:
字节码:
类引用方法(字段、方法和接口方法有类似的常量项结构):
CONSTANT_Methodref_Info {
u1 tag; // CONSTANT_Methodref 0A
u2 class_index; // 常量池有效索引,该索引处的常量项必须是CONSTANT_Class_info结构,
// 表示将字段或方法作为成员的类或接口类型。
u2 name_and_type_index; // 常量池有效索引,该索引处的常量项是CONSTANT_NameAndType_info结构的,
// 表示字段或方法的名称和描述符。
// 如果方法名称以“<”开头那么方法名称就是特殊方法<init>,
// 表示实例初始化方法。这种方法的返回类型必须是void的。
}
例:
#1 = Methodref #3.#15 // java/lang/Object."<init>":()V
常量树:
字节码:
类引用接口方法(字段、方法和接口方法有类似的常量项结构)
CONSTANT_InterfaceMethodref_info {
u1 tag; // CONSTANT_InterfaceMethodref 0B
u2 class_index; // 常量池有效索引,该索引处的常量项必须是CONSTANT_Class_info结构,
// 表示将字段或方法作为成员的类或接口类型。
u2 name_and_type_index; // 常量池有效索引,该索引处的常量项是CONSTANT_NameAndType_info结构的,
// 表示字段或方法的名称和描述符。
// 如果方法名称以“<”开头那么方法名称就是特殊方法<init>,
// 表示实例初始化方法。这种方法的返回类型必须是void的。
}
int和float类型常量(4字节)
CONSTANT_Integer_info {
u1 tag; // CONSTANT_Integer 03
u4 bytes; // bytes项表示int常量的值,值的字节以big-endian(高字节优先)顺序存储。
}
例:
#2 = Integer 1000000
#3 = Integer -2147483648
字节码:
CONSTANT_Float_info {
u1 tag; // CONSTANT_Float 04
u4 bytes; // CONSTANT_Float_info结构的bytes项表示IEEE 754浮点单格式的float常量的值。
// 单格式表示的字节以big-endian(高字节优先)顺序存储。
}
例:
#4 = Float 4.5f
字节码:
long和double类型常量(8字节)
CONSTANT_Long_info {
u1 tag; // CONSTANT_Long 05
u4 high_bytes; // 无符号high_bytes和low_bytes项一起表示long常量的值
u4 low_bytes; // ((long) high_bytes << 32) + low_bytes
// high_bytes和low_bytes都以big-endian(高字节优先)顺序存储
}
CONSTANT_Double_info {
u1 tag; // CONSTANT_Double 06
u4 high_bytes; // high_bytes和low_bytes项一起表示IEEE 754双精度浮点格式中的double值。
u4 low_bytes; // high_bytes和low_bytes项转换为long常量位,
// 等于((long) high_bytes << 32) + low_bytes
}
所有8字节常量占用常量池中的两个常量项。如果CONSTANT_Long_info或CONSTANT_Double_info结构是位于索引n处的常量池项,则池中的下一个可用项位于索引n + 2处。常量池索引n + 1处必须有效,但被认为是不可用的。
访问标志(u2 access_flags)
跟在常量池后面的访问标志是用于表示对此类或接口的访问权限和属性标志掩码。设置时,每个标志的解释如下表所示:
例如:
access_flags = 0x0021 = ACC_PUBLIC|ACC_SUPER(普通类)
access_flags = 0x0601 = ACC_PUBLIC|ACC_INTERFACE|ACC_ABSTRACT(接口【interface】)
ACC_SYNTHETIC 标识这个类并非由用户代码生成。
与ACC_SYNTHETIC标记等价的属性称为Synthetic Attribute,它用于指示当前类、接口、方法或字段由编译器生成,而不在源代码中存在(不包含类初始化函数和实例初始化函数),相同的功能还有一种方式就是在类、接口、方法或字段的访问权限中设置ACC_SYNTHETIC标记。Synthetic Attribute是从JDK 1.1中引入的,主要用于支持内嵌类和接口(Nested classes && Interfaces),这些功能目前都可以使用ACC_SYNTHETIC来表达。ACC_SYNTHETIChe Synthetic Attribute功能相同,但不是同一个东西。
当前类(u2 this_class),常量池有效索引,该索引处的常量项是此类定义的CONSTANT_Class_info结构的类或接口。
this_class = 0x0005,指向第5个常量,Class结构体,即:
#5 = Class #28 // priv/hpl/primary/test/Test
父类(u2 super_class),对于类,super_class的值必须为零或常量池的有效索引。super_class非零,则该索引处的常量项必须是CONSTANT_Class_info结构的,表示此类的直接父类。直接父类或其任何父类都不能在其ClassFile结构的access_flags项中设置ACC_FINAL标志(不可继承)。super_class为零,则此类文件必须表示Object类,这是唯一没有父类的类。对于接口,super_class项的值必须始终是常量池的有效索引,该索引处的常量项必须是CONSTANT_Class_info结构的。
例:
super_class = 0x0006,指向第6个常量,Class结构体,即:
#6 = Class #29 // java/lang/Object
接口个数(u2 interfaces_count)
interfaces_count项的值给出了此类或接口实现或继承的接口数量。
例1:
interfaces_count = 0,这里没有实现接口
例2:
源码(实现接口:IGscPurTransferHeadersService),
字节码(interfaces_count = 1),
接口表(u2 interfaces[interfaces_count])
接口表中每个元素都必须是常量池的有效索引,interfaces[i]索引处的常量项,其中0≤i<interfaces_count,必须是CONSTANT_Class_info结构,表示作为此类或接口的父接口,在源码中依次按从左到右的顺序列出。
例如:
IGscPurTransferHeadersService在常量池索引(interfaces[0] = 193 = 0x00C1),
【注意】
就算是一个接口类,里面声明1个以上的方法时,由于它没有实现接口,它的接口个数仍然是0。
字段个数(u2 fields_count)
fields_count项的值给出了字段表(fields)元素field_info结构的数量。field_info结构表示由此类或接口类型声明的所有字段,包括类变量和实例变量。
例:
字段表(field_info fields[fields_count])
字段表中的每个元素必须是field_info结构,给出该类或接口中字段的完整描述。字段表仅包括由此类或接口声明的那些字段,不包括表示从父类或父接口继承的字段。
方法个数(u2 methods_count)
methods_count项的值给出了方法表中method_info结构的数量。
方法表(method_info methods[methods_count])
方法表中的每个元素必须是method_info结构,它给出了此类或接口中方法的完整描述。如果method_info的access_flags项中未设置ACC_NATIVE和ACC_ABSTRACT标志,则还提供实现该方法的Java虚拟机指令。
method_info结构表示此类或接口类型声明的所有方法,包括实例方法,类方法,实例初始化方法以及任何类或接口初始化方法。方法表不包含表示从超类或超接口继承的方法的项。
属性个数(u2 attributes_count)
attributes_count项的值给出了此类属性表中的属性数。
属性表(attribute_info attributes[attributes_count])
属性表的每个元素必须是attribute_info结构的。
表4.7-C列出了此规范预定义属性,它们出现在ClassFile结构的属性表中。
ClassFile结构属性表中的预定义属性规则在§4.7中给出。
ClassFile结构属性表中的非预定义属性规则在§4.7.1中给出。