Java类文件的结构
Class文件是以8位字节为基础单位的二进制流,各部分中间没有分隔符。遇到8位字节以上的空间数据项时,则会按照高位在前的方式分割成若干个8位字节进行存储。
Class文件采用类似C语言的伪结构体来存储,这种伪结构体只有两种数据类型:无符号数和表。无符号数以u1,u2,u4,u8四种,数字代表字节数。可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串值。表是由多个无符号数或其他表作为数据项构成的复合数据类型,所有表习惯以“info”结尾。表用于描述有层次关系的复合结构数据,整个Class文件本质上就是一张表。
总览如下:
魔数与Class文件的版本 | 1.魔数0XCAFEBABE 2.次版本号和主版本号 |
常量池 | 1.类和接口的全限定名 2.字段的名称和描述符 3.方法的名称和描述符 |
访问标志 | 1.类的访问信息 2.接口的访问信息 |
类索引、父类索引 和接口索引集合 | 存储类、父类、接口的 文件索引 |
字段表集合 | 1.字段作用域 2.是否static 3.可变性 4.并发可见性 5.可否被序列化 6.字段数据类型 7.字段名称 |
方法表集合 | 1.访问标志 2.名称索引 3.描述符索引 4.属性表集合 |
属性表集合 | 1.Code属性 2.Exceptions属性 3.LocalVariableTable属性 4.LineNumberTable属性 5.SourceFile属性 6.ConstantValue属性 7.InnerClasses属性 8.Deprecated和Synthetic属性 9.StackMapTable属性 10.Signature属性 11.BootstrapMethods属性 |
1.魔术与Class文件版本
每个Class文件头四个字节称为魔数(Magic Number),作用是确定这个文件是不是一个Class文件,其值为0xCAFEBABE。紧跟着其后的4个字节存储的是Class文件的版本号:第5个和第6个字节是次版本号,第7个和第8个字节是主版本号。
2.常量池
紧接着主版本号之后的是常量池入口,入口处放置一项u2类型的数据,代表常量池容量计数值(constant_pool_count),计数器从1开始,0是为了满足后面某些值项常量池索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”。
常量池之中主要存放两大类常量:字面量(Literal) 和 符号引用(Symbolic References) 。字面量比较接近于Java语言层面的常量概念。而符号引用则属于编译原理方面的概念,包括了下面三类常量:
- 类和接口的全限定名(Fully Qualified Name)
- 字段的名称和描述符(Descriptor)
- 方法的名称和描述符
常量池中的每一项常量都是一个表,共有14总结构各不相同的表结构数据,这11种表都有一个共同的特点,就是表开始第一位是一个u1类型的标志位(tag,取值为1置12,缺少标志为2的数据类型),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_InterfaceMethodref_info | 11 | 接口中方法的符号引用 |
CONSTANT_NameAndType_info | 12 | 字段或方法的部分符号引用 |
CONSTANT_MethodHandle_info | 15 | 表示方法句柄 |
CONSTANT_MethodType_info | 16 | 标识方法类型 |
CONSTANT_InvokeDynamic_info | 18 | 标识一个动态方法调用点 |
3.访问标志
常量池结束之后,紧接着的两个字节代表访问标志(access_flags),这个标志用于识别一些类或者接口层次的访问信息。具体标志为以及标志的含义如下:
标志名称 | 标志值 | 含义 |
ACC_PUBLIC | 0x0001 | 是否为public类型 |
ACC_FINAL | 0x0010 | 是否被声明为final,只有类可设置 |
ACC_SUPER | 0x0020 | 是否允许使用invokespecial字节码指令的新语意,invokespecial指令的语意在JDK 1.02发生过改变,为了区别这条指令使用哪种语意,JDK 1.02之后编译出来的类这个标志都必须为真 |
ACC_INTERFACE | 0x0200 | 标识这是一个接口 |
ACC_SYNTHETIC | 0x1000 | 标识这个类并非由用户代码产生的 |
ACC_ABSTRACT | 0x0400 | 是否为abstract类型,对于接口或者抽象类来说,此标志值为真,其他类值为假 |
ACC_ANNOTATION | 0x2000 | 标识这是一个注解 |
ACC_ENUM | 0x4000 | 标识这是一个枚举 |
access_flags 中一共有16个标志位可以使用,当前之定义了其中的8个,没用使用到的标志位要求一律为0。
4.类索引、父类索引与接口索引集合
类索引(this_class)和父类索引(super_class)都是一个u2类型的数据,而接口索引集合(interface)是一组u2类型的数据集合,Class文件中由这三项数据来确定这个类的继承关系。类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名。因为Java不支持多重继承,所以父类索引只有一个,除了java.lang.Object外,所有Java类都有父类,因此除了java.lang.Object,所有java类的父类索引都不为0。接口索引集合就用来描述这个类实现了哪些接口,顺序为implements后面从左到右排列在接口索引集合中。
类索引、父类索引和接口索引都按顺序排列在访问标志之后,类索引和父类索引引用两个u2索引值表示,它们各自指向一个类型为CONSTANT_Class_info的类描述符常量,通过CONSTANT_Class_info类型常量中的索引值可以找到定义在CONSTANT_Utf8_info类型的常量中的全限定名字字符串。
对于接口索引集合,入口第一项——u2类型的数据为接口计数器(interfaces_count),表示索引表的容量。如果该类没有实现任何接口,则该计数器值为0,后面索引不在占用任何字节。
5.字段表集合
字段表(field_info)用于描述接口或者类中声明的变量。字段(field)包括类级变量以及实例级变量,但不包括在方法内部声明的局部变量。字段信息包括:字段的作用域(public、private、protected修饰符)、是实例变量还是类变量(static修饰符)、可变性(final)、并发可见性(volatile修饰符,是否强制从主内存读写)、可否被序列化(transient修饰符)、字段数据类型(基本类型、对象、数组)、字段名称,以上修饰符都是布尔类型。字段表结构如图:
类型 | 名称 | 数量 |
u2 | access_flags | 1 |
u2 | name_index | 1 |
u2 | descriptor_index | 1 |
u2 | attributes_count | 1 |
attribute_info | attributes | attributes_count |
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 |
name_index 和 descriptor_index都是对常量池的引用,分别代表这字段的简单名称以及字段和方法的描述符。
attribute_info用于存一些额外信息,如final static int m =123;
方法和字段的描述符作用是用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。根描述规则,基本数据类型(byte、char、double、float、int、long、short、boolean)以及代表无返回值的void类型都用一个大写字符来表示,对象类型使用字符L加对象的全限定名来表示。
标识字符 | 含义 |
B | 基本类型byte |
C | 基本类型char |
D | 基本类型double |
F | 基本类型float |
I | 基本类型 |
J | 基本类型long |
S | 基本类型short |
Z | 基本类型boolean |
V | 特殊类型void |
L | 对象类型,如Ljava/lang/Object |
比如方法 int indexOf(char[]source,int sourceOffset,int sourceCount,char[]target,int targetOffset,int targetCount,int fromIndex)描述符为”([CII[CIII)I”
6.方法表集合
方法的描述和字段的描述几乎采用了完全一致的方式。方法表一次包括了访问标志(access_flags)、名称索引(name_index)、描述符索引(descriptor_index)、属性表集合(attributes)几项。方法表如下:
标志名称 | 标志值 | 含义 |
ACC_PUBLIC | 0x0001 | 方法是否为public |
ACC_PRIVATE | 0x0002 | 方法是否为private |
ACC_PROTECTED | 0x0004 | 方法是否为protected |
ACC_STATIC | 0x0008 | 方法是否为static |
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 | 方法是否是由编译器自动产生的 |
ACC_FINAL | 0x0010 | 方法是否为final |
7.属性表集合
在Class文件、字段表、方法表都可以携带自己的属性表集合,用于描述某些场景专有的信息。
预定义的属性如下:
属性名称 | 使用位置 | 含义 |
Code | 方法表 | Java代码编译成的字节码指令 |
ConstantValue | 字段表 | final关键字定义的常量值 |
Deprecated | 类、方法表、字段表 | 被声明为deprecated的方法和字段 |
Exceptions | 方法表 | 方法抛出的异常 |
EnclosingMethod | 类文件 | 仅当一个类为局部类或者匿名类时才能拥有这个属性,这个属性用于标识这个类所在的外围方法 |
InnerClasses | 类文件 | 内部类列表 |
LineNumberTable | Code属性 | Java源码的行号和字节码指令的对应关系 |
LocalVariableTable | Code属性 | 方法局部变量描述 |
StackMapTable | Code属性 | JDK1.6新增,供新的类型检查验证器检查和处理目标方法的局部变量和操作数栈所需要的类型是否匹配 |
Signature | 类、方法表、字段表 | JDK1.5新增,用于支持泛型的情况下的方法签名 |
SourceFile | 类文件 | 记录源文件名称 |
SourceDebugExtension | 类文件 | JDK1.6新增,用于存储额外的调试信息。比如JSP调试 |
Synthetic | 类、方发表、字段表 | 表示方法或字段为编译器自动生成的 |
LocalVariableTypeTable | 类 | JDK1.5新增,使用特征签名代替描述符 |
RuntimeVisibleAnnotations | 类、方法表、字段表 | JDK1.5新增,为动态注解提供支持 |
RuntimeVisibleParameterAnnotations | 方法表 | JDK1.5新增,类似RuntimeVisibleAnnotations,但作用对象为方法参数 |
AnnotationDefault | 方法表 | JDK1.5新增,用于记录注解类元素的默认值 |
RuntimeInvisibleAnnotations | 方法表 | JDK1.5新增,作用和RuntimeVisibleAnnotations属性作用相反,用于指定哪些注解是运行时不可见的 |
RuntimeInvisibleParameterAnnotations | 方法表 | JDK1.5新增,类似RuntimeInVisibleAnnotations,但作用对象是方法参数 |
BootstrapMethods | 类文件 | JDK1.7新增,用于保存invokedynamic指令引用的引导方法限定符 |