类文件结构
1、Java编译器将.java文件编译成为.class文件,实际上,是Java编译器读取源文件内容,经过一些列检查和分析后,整理成标准的、更方便Java虚拟机读取的字节码文件。
2、在官方jdk中默认的Java编译器是javac.exe,虚拟机是java.exe,java.exe实际上包含了真正的虚拟机HotSpot。
3、class文件是Java语言实现平台无关性、机器无关性和语言无关性的基石。
平台无关性:class文件内容屏蔽了关于平台的差异,即不管是windows系统、Linux系统、还是其它支持的系统,class文件都不会因此有差异。
机器无关性:class文件内容屏蔽了关于机器的差异,即不管是intel的x86指令架构或arm指令架构,不管是32位或者64位,class文件都不会因此有差异。
语言无关性:class文件内容屏蔽了关于语言的差异,不管是c语言、python语言等,借助对应的编译器,class文件都可以正常实现和解读。
类文件的结构
目前,我不关心Java编译器是如何将Java源代码编译成字节码文件的,只关心编译后的字节码的内容。
类型 | 名称 | 数量 | |
u4 | magic | 魔数 | 1 |
u2 | minor_version | 次版本号 | 1 |
u2 | major_version | 主版本号 | 1 |
u2 | constant_pool_count | 常量池容量 | 1 |
cp_info | constan_pool | 常量池 | constant_pool_count |
u2 | access_flags | 访问标志 | 1 |
u2 | this_class | 本类全限定名 | 1 |
u2 | super_class | 父类限定名 | 1 |
u2 | interface_account | 接口索引容量 | 1 |
u2 | interfaces | 接口索引 | interface_account |
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文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在Class文件之中,中间没有添加任何分隔符。
class文件中存储的数据类型分为两种:无符号数和表。
无符号数属于基本的数据类型,以u1、u2、u4、u8来分别代表1个字节、2字节、4个字节和8个字节的无符号数。
表由表项来组成,表项也可能是另一张表。
魔数
魔数是u4类型,即class文件的前4个字节的内容,作用是确定这个文件是否为一个被虚拟机接受的Class文件。魔数的默认值是0xCAFEBABE。
次版本号和主版本号
此版本号和主版本号都是u2类型,分别为class文件的第5、6个字节和第7、8个字节。
Java的版本号是从45开始的,即主版本号都在45及之上。高版本的JDK能向下兼容以前的Class文件,但不能运行以后版本的Class文件,即使文件格式并未发生任何变化,虚拟机也必须拒绝执行超过其版本号的Class文件。
常量池
紧接着主次版本号之后的是常量池入口,由于常量池中常量的数量是不固定的,所以在常量池的入口需要放置一项u2类型的数据,代表常量池容量计数值(constant_pool_count)。注意,该值是从1开始计数,当值为0x0016,即十进制为22,表示的范围是1~21,共21项常量。
常量池中主要存放两大类常量:字面量和符合引用。
常量池中每一项常量都是一个表,在JDK1.7及之后有14种常量池项目类型,每一种项目都有特定的表结构。
常量 | 描述 | 项目 | 类型 | 项目描述 |
CONSTANT_Utf8_info | UTF-8编码的字符串 | tag | u1 | 值为1 |
length | u2 | UTF-8编码的字符串占用的字节数 | ||
bytes | u1 | 长度为length的UTF-8编码的字符串 | ||
CONSTANT_Integer_info | 整型字面量 | tag | u1 | 值为3 |
bytes | u4 | 按照高位在前存储的int值 | ||
CONSTANT_Float_info | 浮点型字面量 | tag | u1 | 值为4 |
bytes | u4 | 按照高位在前存储的float值 | ||
CONSTANT_Long_info | 长整型字面量 | tag | u1 | 值为5 |
bytes | u8 | 按照高位在前存储的long值 | ||
CONSTANT_Double_info | 双精度浮点型字面量 | tag | u1 | 值为6 |
bytes | u8 | 按照高位在前存储的double值 | ||
CONSTANT_Class_info | 类或接口的符合引用 | tag | u1 | 值为7 |
index | u2 | 指向全限定名常量项的索引 | ||
CONSTANT_String_info | 字符串类型字面量 | tag | u1 | 值为8 |
index | u2 | 指向字符串字面量的索引 | ||
CONSTANT_Fieldref_info | 字段的符号引用 | tag | u1 | 值为9 |
index | u2 | 指向声明字段的类或者接口描述符CONSTANT_Class_info的索引项 | ||
index | u2 | 指向字段描述符CONSTANT_NameAndType的索引项 | ||
CONSTANT_Methodref_info | 类中方法的符号引用 | tag | u1 | 值为10 |
index | u2 | 指向声明方法的类描述符CONSTANT_Class_info的索引项 | ||
index | u2 | 指向名称及类型描述符CONSTANT_NameAndType的索引项 | ||
CONSTANT_InterfaceMethodref_info | 接口中方法的符号引用 | tag | u1 | 值为11 |
index | u2 | 指向声明方法的接口描述符CONSTANT_Class_info的索引项 | ||
index | u2 | 指向名称及类型描述符CONSTANT_NameAndType的索引项 | ||
CONSTANT_NameAndType_info | 字段或方法的部分符号引用 | tag | u1 | 值为12 |
index | u2 | 指向该字段或方法名称常量项的索引 | ||
index | u2 | 指向该字段或方法描述符常量项的索引 | ||
CONSTANT_MethodHandle_info | 表示方法句柄 | tag | u1 | 值为15 |
reference_kind | u1 | 值必须在1~9范围,它决定了方法句柄的类型。方法句柄类型的值表示方法句柄的字节码行为 | ||
reference_index | u2 | 值必须是对常量池的有效索引 | ||
CONSTANT_MethodType_info | 标识方法类型 | tag | u1 | 值为16 |
descriptor_index | u2 | 值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示方法的描述符 | ||
CONSTANT_InvokeDynamic_info | 表示一个动态方法调用点 | tag | u1 | 值为18 |
bootstrap_method_attr_index | u2 | 值必须是对当前Class文件中引导方法表的bootstrap_methods[]数组的的有效索引 | ||
name_and_type_index | u2 | 值必须是对当前常量池的有效索引,常量池在该索引处的项必须是CONSTANT_NameAndType_info结构,表示方法名和方法描述符 |
访问标志
在常量池结束之后,紧接着的两个字节代表访问标志,这个标志用于识别 一些类或者接口层次的访问信息。
标志名称 | 标志值 | 含义 |
ACC_PUBLIC | 0x0001 | 是否为public类型 |
ACC_FINAL | 0x0010 | 是否被声明为final类型,只有类可设置 |
ACC_SUPER | 0x0020 | 是否允许使用invokespecial字节码指令的新语意,invokespecial指令的语意在JDB1.2之后发生过改变,为了区别这条指令使用哪种语意,JDK1.0.2之后编译出来的类的这个标志都必须为真。 |
ACC_INTERFACE | 0x0200 | 标识这是一个接口 |
ACC_ABSTRACT | 0x0400 | 是否为abstract类型,对于接口或者抽象类来说,此标志为真,其它类值为假 |
ACC_SYNTHETIC | 0x1000 | 标识这个类并非由用户代码产生的 |
ACC_ANNOTATION | 0x2000 | 标识这是一个注解 |
ACC_ENUM | 0x4000 | 标识这是一个枚举 |
类索引、父类索引和接口索引集合
类索引、父类索引和接口索引集合都按顺序排列在访问标志之后,类索引和父类索引用两个u2类型的索引值表示,它们各自指向一个类型为CONSTANT_Class_info的类描述符常量,通过CONSTANT_Class_info类型的常量中的索引值找到可以定义在CONSTANT_Utf8_info类型的常量中的全限定名字符串。
对于接口索引集合,入口的第一项——u2类型的数据为接口计数器,表示索引表的容量。如果该类没有实现任何接口,则该计数器值为0.后面的索引表不再占用任何字节。
字段表集合
在字段表集合的开头是一个u2类型的数据fields_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_VOLATILE | 0x0040 | 字段是否为volatile |
ACC_TRANSIENT | 0x0080 | 字段是否为transient |
ACC_SYNTHETIC | 0x1000 | 字段是否由编译器自动产生的 |
ACC_ENUM | 0x4000 | 字段是否enum |
name_index和descriptor_index都是对常量池的引用,分别代表着字段的简单名称以及字段和方法的描述符。
方法表集合
在方法表集合的开头是一个u2类型的数据methods_count,表示方法表的个数。
方法表结构与字段表结构一致:
类型 | 名称 | 数量 |
u2 | access_flags | 1 |
u2 | name_index | 1 |
u2 | descriptor_index | 1 |
u2 | attributes_count | 1 |
attribute_info | attributes | attributes_count |
访问标志与字段表的访问标志也很相似:区别在于volatile和transient不能修饰方法,但增加了synchronized、native、strictfp和abstract关键字。
标志名称 | 标志值 | 含义 |
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 | 方法是否是由编译器自动产生的 |
方法中的Java代码,经过编译器编译成字节码后,存放在方法属性表集合中一个名为“Code”的属性里面。
属性表集合
在方法表集合的开头是一个u2类型的数据attributes_count,表示属性表的个数。
对于每个属性,它的名称需要从常量池中引用一个CONSTANT_Utf8_info类型的常量来表示,而属性值的结构则是完全自定义的,只需要通过一个u4的长度去说明属性值所占用的位数即可。
属性表结构:
类型 | 名称 | 数量 |
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u1 | info | attribute_length |
1、Code属性
类型 | 名称 | 数量 |
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | max_stack | 1 |
u2 | max_local | 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 |