Class类文件结构
class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑的排列在class文件之中,中间没有添加任何分隔符,这使得整个Class文件中存储的内容几乎全部是程序运行的必要数据,没有空隙存在。
根据java虚拟机规范的规定,Class文件格式采用一种类似于C语言结构体的伪结构来存储数据,这种伪结构只有两种数据类型:无符号数和表,后面的解析都要以这两种数据类型为基础。
无符号数:属于基本的数据类型,以u1,u2,u4,u8来分别代表一个字节,2个字节等~,可以用来描述数字,索引引用,数量值或者按照UTF-8编码构成字符串值。
表:是由多个无符号数或者其他表作为数据项构成的复合数据类型。表用于描述由层次关系的复合结构的数据,整个Class文件本质上就是一张表。
类型 | 名称 | 数量 |
---|---|---|
u4 | magic | 1 |
u2 | minor_version | 1 |
u2 | major_version | 1 |
u2 | constant_pool_count | 1 |
cp_info | constant_pool | constant_pool_count - 1 |
u2 | access_flags | 1 |
u2 | this_class | 1 |
u2 | super_class | 1 |
u2 | interfaces_count | 1 |
u2 | interfaces | interfaces_count |
u2 | fields_count | 1 |
field_info | fields | fields_count |
u2 | methods_count | 1 |
method_info | methods | methods_count |
u2 | attribute_count | 1 |
attribute_info | attributes | attributes_count |
public class Test {
private int m;
private int incr() {
return m + 1;
}
}
魔术与Class文件的版本号
每个Class文件的头4个字节称为魔数(Magic Number),它的唯一作用是用于确定这个文件是否为一个能被虚拟机接受的Class文件。很多文件存储标准中都是用魔数来进行身份识别。
Class文件的魔数的获得很有“浪漫气息”,值为:0xCAFEBABE。
紧接着魔数的4个字节存储的是Class文件的版本号:第5和第6个字节是次版本号(Minor Version),第7个和第8个字节是主版本号(Major Version)。Java的版本号是从45开始的,JDK1.1之后的每个JDK大版本发布主版本号向上加1(JDK1.0–1.1使用了45.0–45.3的版本号),高版本的JDK能向下兼容以前版本的Class文件,但不能运行以后版本的Class文件,即时文件格式并未发生变化。
常量池
紧接着主次版本号之后的是常量池入口,常量池是Class文件结构中与其他项目关联最多的数据类型,也是占用Class文件空间最大的数据项目之一,同时它还是在Class文件中第一个出现的表类型的数据项目。
由于常量池中常量数量不固定,所以在常量池的入口需要放置一项u2类型的数据,代表常量池容量计数值(constant_pool_count)。这个容量计数是从1而不是0开始的。这样做是为了满足后面某些指向常量池的索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”的意思,这种情况就可以把索引值置为0来表示
常量池中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。
字面量比较接近于Java语言层面的常量概念,如文本字符串、被声明为final的常量值等。
而符号引用则属于编译原理方面的概念,包括下面三类常量:
类和接口的全限定名(Fully Qualified Name)
字段的名称和描述符(Descriptor)
方法的名称和描述符
常量池中每一项常量都是一个表,共有14种结构各不相同的表结构数据,这14种表都有一个共同的特点,就是表开始的第一位是一个u1类型的标志位,代表当前这个常量属于哪种常量类型,14种常量类型所代表的具体含义如下表:
类型 | 标志 | 含义 |
---|---|---|
CONSTANT_Utf8_info | 1 | UTF-8编码的字符串 |
CONSTANT_Integer_info | 3 | 整型字面量 |
CONSTANT_Float_info | 4 | 浮点型字面量 |
CONSTANT_Long_info | 5 | 长整形字面量 |
CONSTANT_Double_info | 6 | 双精度浮点型字面量 |
CONSTANT_Class_info | 7 | 类或接口的符号引用 |
CONSTANT_String_info | 8 | 字符串类型字面量 |
CONSTANT_Fieldref_info | 9 | 字段的符号引用 |
CONSTANT_Methodref_info | 10 | 类中方法的符号引用 |
CONSTANT_InterfaceMethod_info | 11 | 接口中方法的符号引用 |
CONSTANT_NameAndType_info | 12 | 字段或方法的部分符号引用 |
CONSTANT_MethodHandle_info | 15 | 表示方法句柄 |
CONSTANT_MethodType_info | 16 | 标识方法类型 |
CONSTANT_InvokeDynamic_info | 18 | 表示一个动态方法调用点 |
14种常量类型各自均有自己的结构。
常量 | 项目 | 类型 | 含义 |
CONSTANT_Utf8_info | 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_info的索引项 | |
CONSTANT_Methodref_info | tag | U1 | 10 |
index | U2 | 指向声明方法的类描述符CONSTANT_Class_info的索引项 | |
index | U2 | 指向名称及类描述符CONSTANT_NameAndType_info的索引项 | |
CONSTANT_InterfaceMethod_info | tag | U1 | 11 |
index | U2 | 指向声明方法的接口描述符COSNTANT_Class_info的索引项 | |
index | U2 | 指向名称及类描述符CONSTANT_NameAndType_info的索引项 | |
CONSTANT_NameAndType_info | tag | U1 | 12 |
index | U2 | 指向该字段或方法名称常量池的索引 | |
index | U2 | 指向该字段或方法描述符常量池的索引 | |
CONSTANT_MethodHandle_info | tag | U1 | 15 |
reference_kind | U2 | 值必须在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_attrindex | U2 | 值必须是对当前Class文件中引导方法表的bootstrap_methods[]数组的有效索引 | |
name_and_type_index | U2 | 值必须是对当前常量池的有效索引,常量池在该索引处的项必须是COSTANT_NameAndType_info结构,表示方法名和方法描述符 |
类或者接口层次的访问信息
access_flag
在常量池结束之后,紧接着的2个字节代表访问标志(access_flag),这个标志用于识别一些类或接口层次的访问信息,包括:这个Class是类还是接口,是否定义为public类型,abstract类型,如果是类的话,是否声明为final,等等。每种访问信息都由一个十六进制的标志值表示,如果同时具有多种访问信息,则得到的标志值为这几种访问信息的标志值的逻辑或。
标志名称 | 标志值 | 含义 |
ACC_PUBLIC | 0x0001 | 是否是public |
ACC_FINAL | 0x0010 | 是否被声明为final,只有类可以设置 |
ACC_SUPER | 0x0020 | 是否允许使用invokespecial字节码指令的新语义,JDK1.0.2之后编译出来的类的这个标志默认为真 |
ACC_INTERFACE | 0x0200 | 标识是一个接口 |
ACC_ABSTRACT | 0x0400 | 是否是abstract,对于接口和抽象类来说为真,其他类都为假 |
ACC_SYNITHETIC | 0x1000 | 标识这个类并非由用户代码产生 |
ACC_ANNOTATION | 0x2000 | 标识这是一个注解 |
ACC_ENUM | 0x4000 | 标识这是一个枚举类 |
类索引、父类索引与接口索引集合
由于Java中是单继承,所以父类索引只有一个;但Java类可以实现多个接口,所以接口索引是一个集合。
字段表集合
字段表用来描述接口或类中声明的变量。字段包括类级变量和实例级变量,但不包括方法内变量。
类型 | 名称 | 数量 |
---|---|---|
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 |
字段描述类型
标识字符 | 含义 |
---|---|
B | byte |
J | long |
C | char |
S | short |
D | double |
Z | boolean |
F | float |
V | void |
I | int |
L | 对象类型,如Ljava/lang/Object |
字段表集合中不会列出从父类或接口中继承来的字段,但有可能会出现原本Java程序中没有的字段。比较典型的例子是内部类,为了在内部类中保持对外部类的访问性,会增加一个指向外部类实例的字段。另外,在Java语言中字段无法重载,也就是字段名不能重复,即使两个字段的数据类型、修饰符都不相同。不过对于字节码来说,如果两个字段的描述符不一致,那么就可以有重复的字段名。
方法表
方法表结构与字段表结构几乎相同,仅在访问标志和属性表集合的可选项中有所区别。方法里的java代码存放在方法属性表中名为Code的属性中。
当描述符用来描述方法时,按照先参数列表,后返回值的顺序描述,参数列表按照参数的严格顺序放在一组小括号"()"内。比如方法void inc()的描述符是:()V。方法java.lang.String toString()的描述符是:()Ljava/lang/String。方法int indexOf(char[] source,int sourceOffset,int sourceCount,char[] target,int targetOffset,int targetCount,int fromIndex)的描述符是:([CII[CIII)I。
(扩展网上查的:
Java在编译之后会在字节码文件中生成
- 父类非静态变量初始化
- 父类非静态代码块
- 父类构造函数
- 子类非静态变量初始化
- 子类非静态代码块
- 子类构造函数
所谓收敛到方法中的意思就是,将这些操作放入到 中去执行
Java在编译之后会在字节码文件中生成
- 父类静态变量初始化
- 父类静态代码块
- 子类静态变量初始化
- 子类静态代码块
- 父类静态变量初始化
- 父类静态代码块
- 子类静态变量初始化
- 子类静态代码块
- 父类变量初始化
- 父类代码块
- 父类构造函数
- 子类变量初始化
- 子类代码块
- 子类构造函数
)
属性表
属性表在前面出现了多次,在class文件、字段表和方法表都可以携带自己的属性表集合,来描述某些场景专有的信息。
属性表集合存在的位置也是不确定的,不仅可以存储在class文件结尾处,还可以作为数据项存在于类、方法表集合和字段表集合中。
常见的属性:
属性名称 | 使用位置 | 含义 | Since |
Code | 方法表 | Java代码编译后的字节码指令 | 《Java虚拟机规范(第二版)》 |
ConstantValue | 字段表 | final关键字定义的常量值 | 《Java虚拟机规范(第二版)》 |
Deprecated | 类、方法表、字段表 | 被声明为deprecated的方法和字段 | 《Java虚拟机规范(第二版)》 |
Exception | 方法表 | 方法抛出的异常 | 《Java虚拟机规范(第二版)》 |
EnclosingMethod | 类文件 | 仅当一个类为局部类或匿名类时才能拥有这个属性,来标识这个类所在的外围方法 | 《Java虚拟机规范(第二版)》 |
InnerClasses | 类文件 | 内部类列表 | 《Java虚拟机规范(第二版)》 |
LineNumberTable | Code属性 | Java源码的行号与字节码指令的对应关系 | 《Java虚拟机规范(第二版)》 |
LocalVariableTable | Code属性 | 方法的局部变量描述 | 《Java虚拟机规范(第二版)》 |
StackMapTable | Code属性 | 供新的类型检查验证器检查和处理目标方法的局部变量和操作数栈所需要的类型检查 | JDK 1.6 |
Signature | 类、方法表、字段表 | 用于保存泛型中的类型信息 | JDK 1.5 |
SourceFile | 类文件 | 记录源文件名称 | 《Java虚拟机规范(第二版)》 |
SourceDebugExtension | 类文件 | 用于存储额外的调试信息 | 《Java虚拟机规范(第二版)》 |
Synthetic | 类、方法表、字段表 | 标识方法或字段为编译器自动生成 | 《Java虚拟机规范(第二版)》 |
LocalVariableTypeTable | 类 | 使用特征签名代替描述符,为了描述泛型参数化类型 | JDK 1.5 |
RuntimeVisibleAnnotations | 类、方法表、字段表 | 为动态注解提供支持 | JDK 1.5 |
RuntimeInvisibleAnnotations | 类、方法表、字段表 | 与RuntimeVisibleAnnotations作用相反 | JDK 1.5 |
RuntimeVisbleParameterAnnotations | 方法表 | 与RuntimeVisibleAnnotations类似 | JDK 1.5 |
RuntimeInvisbleParameterAnnotations | 方法表 | 与RuntimeInvisibleAnnotations类似 | JDK 1.5 |
AnnotationDefault | 方法表 | 用于记录注解类元素的默认值 | JDK 1.5 |
BootstrapMethods | 类文件 | 用于保存invokedynamic指令引用的引导方法限定符 | JDK 1.7 |
由于属性表集合的限制较小,每个属性都会有自己的格式,因此class文件对于属性的格式要求也比较宽松,只需要满足一些特定的条件即可。下表是属性的结构:
类型 | 名称 | 数量 |
U2 | attribute_name_index | 1 |
U4 | attribute_length | 1 |
U1 | info | attribute_length |
(1)Code属性
最常用的属性恐怕就是Code属性了,因为大多数的方法都会有编译后的字节码指令,这些指令就存储在方法表中的Code属性中。如果一个Java程序的信息可以分为代码(方法体中的代码)和元数据(包括类、字段、方法定义以及其它信息),那么Code属性存储的就是代码,其它所有的结构存储的都是元数据。不过并非所有的方法表都有这个Code属性,比如接口或抽象类中的方法表就不存在Code属性(JDK 1.8中的接口也可以定义方法了)。Code属性的结构如下:
类型 | 名称 | 数量 |
U2 | attribute_name_index | 1 |
U4 | attribute_lenght | 1 |
U2 | max_stack | 1 |
U2 | max_locals | 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 |
(2)LinNumberTable
(3)LocalVariableTable
(4) SourceFile