类文件结构

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
posted @ 2019-10-19 14:42  蓝色风扇  阅读(168)  评论(0编辑  收藏  举报