类文件结构分析

class类文件结构

参考:https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.7.5 jdk11

 前面说过,任何语言只要能被编译成符合规范的字节码文件,就可以在虚拟机上运行。字节码文件是java规范中规定的,一个class文件必定对应着一个类或者接口的信息,但是反过来则不一定,因为类或者接口可以动态生成,直接被类加载器加载。

 class文件是一组以8字节为基础单位的二进制流,代码中各个部分严格按照顺序排列在文件中,没有任何分隔符。当遇到需要占用8字节以上空间的数据项时,会按照高位在前的方式分割成若干个8字节进行存储。

 class文件由表中所展示的这些项组成。u1、u2……代表1个字节、2个字节等。“_info”结尾的代表是表,复合数据结构。

class文件结构

1 魔数和版本号

 class文件的头4个字节被称为魔数(Magic Number),它用来标识该文件是否为有效的class文件,class文件的魔数值为“0xCAFABABE”,我们可以看到任何class文件都是以“0xCAFABABE”开头。

 魔数之后是java的版本号,第5、6字节为次版本号,第7、8字节为主版本号。jdk版本号从45开始,jdk版本号和java版本的对应规则如下图所示。魔数版本号在验证阶段,都会对其进行校验。

1
2
3
4
5
6
7
8
9
JDK版本			版本号
JDK 6 50
JDK 7 51
JDK 8 52
JDK 9 53
JDK 10 54
JDK 11 55
JDK 12 56
JDK 13 56

jdk版本号对应关系图

 比如有以下例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.hk7.memory;

import java.io.Serializable;

/**
* HelloWorld
*
* @author zhoumi
* @date 2020/10/13
*/
public class ClassTest implements Serializable {
private int num;

public int add() {
return num + 1;
}
}

 使用010editor打开,可以看到以0xCAFEBABE开头,主版本号是0x0034 = 52,表示该class文件可以被jdk8及其以上版本的虚拟机执行。

字节码文件

2 常量池

 在版本号之后是常量池,是class文件结构中与其他项目关联最多的数据,也是占用class文件空间最大的数据项目之一。

 由于常量池中常量的数量是不固定的,所以在常量池的入口处必须放一个2字节的数表示常量池中常量的个数。上图中可以看出该class文件常量池中常量的个数为0x0018 = 24,表示有23个常量,索引范围为1~23。索引从1开始是因为设计者将0位置空出来以满足“不引用常量池中的任何项目”需求。常量池是唯一一个索引从1开始计算的集合。

 常量池中存放的内容为:字面量(Literal)和符号引用(Symbolic References)。

 字面量表示文本字符串、被声明为final的常量值等。

 符号引用包括以下几类:

  • 被模块导出或者开放的包
  • 类和接口的全限定名
  • 字段的名称和描述符(Description)
  • 方法的名称和描述符
  • 方法句柄和方法类型(Method Handle、Method Type、Invoke Dynamic)
  • 动态调用点和动态常量(Dynamically-Computed Call Site、Dynamically-Computed Constant)

 常量池中吗,每一个常量项都是一个表,最初有11种结构,后面为了支持动态语言调用、支持模块化系统又加入了6种,截止JDK13,常量表中有17种不同类型的常量,每种常量类型的结构都不同,如下表所示:

常量 描述 项目 类型 描述
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_info的索引项
CONSTANT_Methodref_info 类中方法的符号引用 tag u1 值为10
index u2 指向声明方法的类描述符CONSTANT_Class_info的索引项
index u2 指向名称及类型描述符CONSTANT_NameAndType_info的索引项
CONSTANT_InterfaceMethodref_info 接口中方法的符号引用 tag u1 值为11
index u2 指向声明方法的接口描述符CONSTANT_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 u1 值在[1,9],决定方法句柄的类型。方法句柄类型的值表示方法句柄的字节码行为
reference_index u2 值必须是对常量池的有效引用
CONSTANT_MethodType_info 表示方法类型 tag u1 值为16
descriptor_index u2 值必须是对常量池的有效引用,且必须为CONSTANT_utf8_info结构,表示方法的描述符
CONSTANT_Dynamic_info 表示一个动态计算常量 tag u1 值为17
bootstrap_method_attr_index u2 值必须是对当前class文件中引导方法表的boostrap_methods[]数据的有效索引
name_and_type_index u2 值必须是对常量池的有效引用,且必须为CONSTANT_NameAndType_info结构,表示方法名和方法描述符
CONSTANT_InvokeDynamic_info 表示一个动态方法调用点 tag u1 值为18
bootstrap_method_attr_index u2 值必须是对当前class文件中引导方法表的boostrap_methods[]数据的有效索引
name_and_type_index u2 值必须是对常量池的有效引用,且必须为CONSTANT_NameAndType_info结构,表示方法名和方法描述符
CONSTANT_Module_info 表示一个模块 tag u1 值为19
name_index u2 值必须是对常量池的有效引用,且必须为CONSTANT_utf8_info结构,表示模块名字
CONSTANT_Package_info 表示一个模块中开放或者导出的包 tag u1 值为20
name_index u2 值必须是对常量池的有效引用,且必须为CONSTANT_utf8_info结构,表示包名称

 我们还是看上面的例子,0A 00 04 00 13,常量池常量个数位之后tag是 0x0A = 10,可知这是一个CONSTANT_Methodref_info类中方法的符号引用常量。再后面是2个index,类型为u2、u2,也就是0x0004 = 4 和 0x0013 = 19,分别指向常量池中的第4个常量项和第19个常量项,翻译完整个字节码,发现第4个常量是CONSTANT_Class_info类型,第19个常量是CONSTANT_NameAndType_info类型,符合定义,以此类推直至解析完整个常量池。

 我们采用javap -v xxx.class来解析字节码如下,可以看到第1个常量是Methodref,第4个是Class,第19个是NameAndType,和我们手动计算的一致。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Constant pool:
#1 = Methodref #4.#19 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#20 // com/hk7/memory/ClassTest.num:I
#3 = Class #21 // com/hk7/memory/ClassTest
#4 = Class #22 // java/lang/Object
#5 = Class #23 // java/io/Serializable
#6 = Utf8 num
#7 = Utf8 I
#8 = Utf8 <init>
#9 = Utf8 ()V
#10 = Utf8 Code
#11 = Utf8 LineNumberTable
#12 = Utf8 LocalVariableTable
#13 = Utf8 this
#14 = Utf8 Lcom/hk7/memory/ClassTest;
#15 = Utf8 add
#16 = Utf8 ()I
#17 = Utf8 SourceFile
#18 = Utf8 ClassTest.java
#19 = NameAndType #8:#9 // "<init>":()V
#20 = NameAndType #6:#7 // num:I
#21 = Utf8 com/hk7/memory/ClassTest
#22 = Utf8 java/lang/Object
#23 = Utf8 java/io/Serializable

3 访问标志

 常量池之后紧接着的是访问标志,占2个字节,用于识别一些类或者接口层次的访问信息。标识符的类型和含义如下表所示:

标志名称 标志值 含义
ACC_PUBLIC 0x0001 是否为public类型
ACC_FINAL 0x0010 是否被声明为final,只有类可设置
ACC_SUPER 0x0020 是否允许使用invokespecial字节码指令的新语义。jdk1.1之后该值必须为真
ACC_INTERFACE 0x0200 标识这是一个接口
ACC_ABSTRACT 0x0400 是否为abstract类型。接口和抽象类为真,其他为假
ACC_SYNTHETIC 0x1000 标识这个类并非由用户代码产生的
ACC_ANNOTATION 0x2000 标识这是一个注解
ACC_ENUM 0x4000 表示这是一个枚举
ACC_MODEULE 0x8000 标识这是一个模块

 标志符占2字节,16位,但是目前只定义了9个,未使用到的标志位一律为0。再看前面说到的字节码常量池之后是0x0021 = 0x0001|0x0020,查上表可知public类型,可使用invokespecial指令。

ACC_SUPDER:https://blog.csdn.net/dshf_1/article/details/105787923

4 类索引、父类索引和接口索引集合

 访问标志之后接着是类索引(this_class)和父类索引(super_class),它们都是u2类型的数据,接口索引集合(interfaces)是一组u2类型的数据集合,Class文件中这三项确定该类型的继承关系

  • 类索引:确定该类的全限定名。
  • 父类索引:确定该类父类的全限定名。除了java.lang.Object之外,所有的类都有父类(java只能继承一个父类,因此父类索引只有一个,而接口可以实现多个,因此接口索引是个集合),因此除了Object,其他类的父类索引都不为0。
  • 接口索引:描述该类实现的接口(implements之后的所有接口,顺序排列,如果是接口则是extends之后的接口),可以有多个,是接口集合。

 this_class和super_class均指向CONSGANT_class_info的的类描述符常量,通过指向的CONSGANT_class_info类型的常量中的索引值,可以找到定义在CONSGANT_utf8_info类型常量中的全限定名字符串。

 还是上面那个例子,访问标志之后的4个字节为0x00030x0004,表示类索引和父类索引指向常量池中的#3#4个常量,查常量池可以看到,他们确实是class类型的常量,#3又指向#21#21为utf8类型常量,值为com/hk7/memory/ClassTests。#4指向#22#22为utf8常量,值为java/lang/Object。这与规定的一致。

 再之后是接口索引个数,值为0x0001,说明本类没有实现了1个接口,接口索引个数之后紧接着接口的引用0x0005,查常量表#5指向#23,#23为utf8的字符串java/io/Serializable,说明它实现了java/io/Serializable接口,和源码一致。

5 字段表集合

 接口索引之后是字段表(field_info)信息,它描述类或者接口声明的变量,描述作用域(public、private)、变量类型(static静态变量、实例变量)、可变性(final)、并发可见性(volatile)、序列化(transient)、字段数据类型(基本类型、对象、数据)等信息。修饰符等可以用标志位来表示,而字段名、字段类型等无法固定,只能指针指向常量池中的常量来表示。具体的字段表结构如下:

名称 类型 说明 数量 具体
access_flags u2 类或接口的的某些修饰符 1
name_index u2 字段的简单名称 1
descriptor_index u2 字段描述符 1
attributes_count u2 字段属性个数 1
attributes attributes_info 字段属性集合 attributes_count 具体看1.7属性表集合

 由于java语法限制,访问标志必须满足以下规则:

  • ACC_PUBLIC、ACC_PRIVATE和ACC_PROTECTED只能三选一
  • ACC_FINAL和ACC_VOLATILE只能二选一
  • ACC_PUBLIC、ACC_STATIC、ACC_FINAL必须有

 字段或方法的简单名称是指去掉类型和参数修饰的名字,比如例子中没有字段,只有方法main()方法的简单名称就是main。

 字段或者方法的描述符是用来描述字段的数据类型、方法参数列表和返回值的,上图可以看到描述符和类型的对应关系。数组类型,每个维度都会有一个前置的[来描述,比如java.lang.String[][]类型的二维数组被描述为[[Ljava/lang/String;,再比如一个整型数组int[]会描述为[I

 字段的属性是指final、static等修饰符。后面1.7会具体讲解。

 再继续看上面的例子,接口描述之后是字段长度位,可以看到值为0x0001=1,说明方法只有1个变量。后面跟着变量的具体描述,access_flags0x0002表示private修饰;name_index0x0006表示指向常量池#6,查常量表看到#6为utf8的字符串num;descriptor_index0x0007表示指向常量池#7,查常量表可知#7是utf8的I,上图看到了,I是int的描述符,再后面是字段属性个数 0x0000=0,代表没有属性修饰。因此我们推断这一段代码为private int m;

6 方法表集合

 字节码文件中,方法表的结构和上述字段表一致,依次是访问标志(access_flag)、名称索引(name_index)、描述符索引(descriptor_index)和属性表集合。区别在于访问标志选项有差别,volatile和transient无法修饰方法,方法表中没有相应标志,多了synchronized、native、strictfp和abstract相关标志,具体结构如下:

名称 类型 说明 数量 具体
access_flags u2 方法的修饰符 1
name_index u2 方法名称 1
descriptor_index u2 方法描述符 1
attributes_count u2 方法属性个数 1
attributes attributes_info 方法属性集合 attributes_count 具体看1.7属性表集合

 继续分析例子,attributes_count之后是方法个数0x0002表示有2个方法。

  • method1:access_flag0x0001=1表示public方法。name_index0x0008指向常量池#8,#8是utf8常量<init>,descriptor_index0x0009指向常量池#9,#9是utf8()V。再后面attributes_count0x0001表示init方法有1个属性修饰,后面是具体的属性结构0x0001=10,查常量池#10位utf8的Code,code里面存储的是方法里面具体的代码。这部分东西很多,具体请看1.7 属性表集合。通过上述我们推断方法1部分源码为public void init()
  • method2:ccess_flag0x0001=1表示public方法。name_index0x000F指向常量池#15,#15是utf8常量add,descriptor_index0x0010指向常量池#16,#16是utf8()I。再后面attributes_count0x0001表示init方法有1个属性修饰,后面是具体的属性结构0x000A=10,查常量池#10位utf8的Code,code里面存储的是方法里面具体的代码。通过上述我们推断方法2部分源码为public int add()。与源码一致。【可使用010editor的class模板打开class文件查看具体字节码】

7 属性表集合

 属性表在class文件、字段表和方法表中都会出现,用来描述专有的信息。《java虚拟机规范》对属性表没有像字段表、方法表那样严格限制顺序,只要不和已有属性名重复,都可以编译器都可以写入自己定义的属性信息,java虚拟机运行时会忽略掉它不认识的属性。

 属性的名称必须从常量池中引用uft8常量来表示,但是属性值的结构则不用,用长度说明属性值所占的位数即可。属性值的结构如下所示:

1
2
3
4
5
attribute_info {
u2 attribute_name_index; # 属性名称
u4 attribute_length; # 属性表长度
u1 info[attribute_length]; # 属性表具体内容
}

 java虚拟机预定义的属性如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
属性名                                      含义                                                                                     使用位置
ConstantValue || 由final关键字定义的常量值 || 字段表
Code || java代码编译成的字节码指令 || 方法表
StackMapTable || jdk6新增属性,验证局部变量和操作数栈类型是否匹配 || Code属性
Exceptions || 方法抛出的异常列表 || 方法表
InnerClasses || 内部类列表 || 类文件
EnclosingMethod || 只有局部类和匿名类才拥有该属性,标志这个类所在的外围方法 || 类文件
Synthetic || 标识方法或字段为编译器自动生成的 || 类、方法表、字段表
Signature || jdk5新增属性,用于支持泛型的类、方法、字段的签名 || 类、方法表、字段表
SourceFile || 记录源文件名称 || 类文件
SourceDebugExtension || jdk5新增属性,存储额外的调试信息 || 类文件
LineNumberTable || java源码行号与字节码指令的对应关系 || Code属性
LocalVariableTable || 方法的局部变量描述 || Code属性
LocalVariableTypeTable || jdk5新增属性,使用特征签名代替描述符,为支持泛型添加的属性 || 类文件
Deprecated || 被声明为deprecated的方法和字段 || 类、方法表、字段表
RuntimeVisibleAnnotations || jdk5新增属性,为动态注解提供支持 || 类、方法表、字段表
RuntimeInvisibleAnnotations || 同上相反 || 类、方法表、字段表
RuntimeVisibleParameterAnnotations || 类似于RuntimeVisibleAnnotations,作用于方法 || 方法表
RuntimeInvisibleParameterAnnotations || 同上相反 || 方法表
RuntimeVisibleTypeAnnotations || jdk8新增属性,用于指明哪些类是运行时(即反射调用)可见的 || 类、方法表、字段表
RuntimeInvisibleTypeAnnotations || 同上相反 || 类、方法表、字段表
AnnotationDefault || jdk5新增属性,用于记录注解类元素的默认值 || 方法表
BootstrapMethods || jdk7新增属性,用于保存invokedynamic指令引用的引导方法限定符 || 类文件
MethodParameters || jdk8新增属性,编译时加上-parameters参数可以将方法名称编译进Class文件中,并可运行时获取 || 方法表
Module || jdk9新增属性,用于记录一个Module的名称以及相关信息(requires、exports、opens、uses、provides) || 类文件
ModulePackages || jdk9新增属性,用于记录一个模块中所有被exports或者opens的包 || 类文件
ModuleMainClass || jdk9新增属性,用于指定一个模块的主类 || 类文件
NestHost || jdk11新增属性,用于支持嵌套类的反射和访问控制的API,内部类通过该属性得知自己的宿主类 || 类文件
NestMembers || jdk11新增属性,用于支持嵌套类的反射和访问控制的API,内部宿主类通过该属性得知自己有哪些内部类 || 类文件

7.1 ConstantValue属性

 ConstantValue属性的作用是通知虚拟机自动为静态变量赋值。只有被static修饰的变量才能使用该属性。实例变量(int x = 123)和静态变量(static int x = 123)的赋值方式和时机不同。前者是在实例构造器<init()>中赋值的。而静态变量的赋值有2种方式:

  • 在类构造器<cinit()>中赋值
  • 使用ConstantValue属性赋值

 目前Oracle实现的javac编译器的实现方式是:如果变量同时使用final和static修饰(java语法上的常量)且类型为基本类型或者String类型,则使用ConstantValue属性赋值。否则会在<cinit()>中赋值。

 《java虚拟机规范》中没有强制要求final修饰的变量必须设置ACC_FINAL标志,只要求具有ConstantValue属性的字段必须设置ACC_STATIC标志。final关键字的要求是javac编译器自己加入的。

 ConstantValue的结构如下:

1
2
3
4
5
ConstantValue_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 constantvalue_index; // 对常量池中字面量常量的引用。
}

 上述提到被final和static修饰的基本类型以及String类型的变量,才拥有ConstantValue属性,因此constantvalue_index指向的字面量常量只能是以下几种:

1
2
3
4
5
CONSTANT_Integer(int, short, char, byte, boolean)
CONSTANT_Float(float
CONSTANT_Long(long
CONSTANT_Double(double
CONSTANT_String(String

7.2 Code属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Code_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 max_stack;
u2 max_locals;
u4 code_length;
u1 code[code_length];
u2 exception_table_length;
{ u2 start_pc;
u2 end_pc;
u2 handler_pc;
u2 catch_type;
} exception_table[exception_table_length];
u2 attributes_count;
attribute_info attributes[attributes_count];
}

 从上面Code属性结构可以看到如下信息

  • attributes_name_index(u2):指向CONSTANT_Utf8_info常量的索引,此常量值固定为“Code”,它代表该属性的名称
  • attributes_length(u4):指示了属性值长度,属性值长度=整个属性表长度 - 6,6是attributes_name_index和attributes_length所占长度
  • max_stack(u2):操作数栈的深度。方法执行的任意时刻,操作数栈都不会超过这个深度。jvm运行时候根据该值分配栈帧中操作数栈的深度。
  • max_locals(u2):局部变量表所需要的存储空间。max_locals的单位是槽Slot,是虚拟机为局部变量分配内存的最小单位。具体可看jvm之内存管理中的栈帧局部变量表部分。
  • code_length(u4):code_length和code用来存储源码编译后生成的字节码指令。code_length代表字节码长度。理论上是u4长度,但是实际上只使用了u2长度。《规范》规定了一个方法不允许超过65535条字节码指令,超过javac编译器会拒绝编译。
  • code(u2):存储用于虚拟机字节码指令的一系列字节流。每个字节码指令是一个u1类型的单字节,当虚拟机读取到code中的一个字节码时,就知道这个字节码的含义,以及它的携带的参数和如何解析。u1类型取值范围为0x00~0xFF,即一共可以表达256条指令。目前《java虚拟机规范》定义了约200条编码值对应的指令含义。具体可查 虚拟机字节码指令表
  • exception_table_length(u2):异常表长度
  • exception_table(exception_info):异常表由4个项组成,分别是start_pc(u2)、end_pc(u2)、handler_pc(u2)和catch_type(u2)。
  • attributes_count(u2):略
  • attributes(attributes_info):字段属性表的具体内容详细见7.

 Code属性是Class文件中最重要的属性,如果把java程序中的信息分为代码(Code,方法体中的代码)和元数据(类、字段、方法定义等),那么在整个class文件中,Code属性用于描述代码,其他所有数据都用于描述元数据。

 继续前面的例子分析。继续分析add()的Code部分,attribute_name_index0x000A=10表示指向常量池#10,查常量池得#10为Code,表示这是Code代码,后面进入具体的代码结构。紧接着attribute_length0x00000031=49表示代码长度为49。再后max_stack0x0002=2表示add方法操作数栈最大深度为2,再后面max_locals0x0001=1表示局部变量表所需存储空间为1个槽。再后面code_length0x00000007=7表示字节码长度为7,即7个指令。接下来具体的7个字节码指令和气代表的含义分别为

1
2
3
4
5
6
7
0x2A    aload_0       将第一个引用类型本地变量推送至栈顶,也就是加载num
0xB4 getfield 获取指定类的实例域,并将其值压入栈顶,将num入栈
0x00 nop 什么都不做
0x02 iconst_m1 将int-1推送至栈顶,这是啥?num的默认值吗?
0x04 iconst_1 将int1推送至栈顶
0x60 iadd 将栈顶两int型数值相加并将结果压入栈顶,num+1
0xAC ireturn 从当前方法返回intreturn

 在之后是关于异常表的信息exception_table_length0x0000表示没有异常。在之后是attributes_count0x0002表示属性长度为2,之后

 使用javap -v ClassTest.class反汇编,可以得到如下关于add的信息。args_size=1是this所占的Slot。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public int add();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: getfield #2 // Field num:I
4: iconst_1
5: iadd
6: ireturn
LineNumberTable:
line 15: 0
LocalVariableTable:
Start Length Slot Name Signature
0 7 0 this Lcom/hk7/memory/ClassTest;

7.3 StackMapTable属性

 StackMapTable属性是jdk6新增的属性,它很复杂,位于Code属性的属性表中。它会在虚拟机类加载的字节码验证阶段被类型检查器(Type Checker)使用,其目的在于代替以前比较消耗性能的基于数据流分析的类型推导验证器(加快类加载的字节码验证阶段)。

 StackMapTable属性中包含零到多个栈映射帧(Stack Map Frame),每个栈帧代表一个字节码偏移量,用于表示执行到该字节码时局部变量表和操作数栈的验证类型。类型检查器会检查StackMapTable来确定一段字节码指令是否符合逻辑约束。StackMapTable的结构如下所示:

1
2
3
4
5
6
StackMapTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 number_of_entries;
stack_map_frame entries[number_of_entries];
}

 规定版本号>=50.0的class文件,Code属性必须带有StackMapTable属性,如果没有则表示默认带了一个number_of_entries=0的StackMapTable属性。一个方法的Code属性只有一个StackMapTable属性,否则抛出异常。

 StackMapTable具体见栈帧的内部结构。

7.4 Exceptions属性

 Exceptions属性和Code属性平级,不同于Code属性中的异常表。Exceptions的作用主要是列举方法中可能抛出的受检异常(Checked Exceptions),也就是throws后面的异常。它的结构如下:

1
2
3
4
5
6
Exceptions_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 number_of_exceptions; //受检异常数量
u2 exception_index_table[number_of_exceptions]; //指向常量池中CONSTANT_Class_info型的索引,代表该异常类型
}

7.5 InnerClass属性

 InnerClass属性用于记录内部类和宿主类之间的关联。它的结构如下:

1
2
3
4
5
6
7
8
9
10
InnerClasses_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 number_of_classes; //需要记录的内部类信息个数
{ u2 inner_class_info_index; //指向常量池中的CONSTANT_Class_info型索引
u2 outer_class_info_index; //指向常量池中的CONSTANT_Class_info型索引
u2 inner_name_index; //指向常量池中的CONSTANT_Utf8_info型索引
u2 inner_class_access_flags;
} classes[number_of_classes];
}

 inner_class_access_flags是内部类的访问标志,取值范围如下:

1
2
3
4
5
6
7
8
9
10
11
Flag Name				Value		Interpretation
ACC_PUBLIC 0x0001 内部类是否为public
ACC_PRIVATE 0x0002 private
ACC_PROTECTED 0x0004 protected
ACC_STATIC 0x0008 static
ACC_FINAL 0x0010 final
ACC_INTERFACE 0x0200 接口
ACC_ABSTRACT 0x0400 abstract
ACC_SYNTHETIC 0x1000 内部类是否自动生成(非用户代码生成)
ACC_ANNOTATION 0x2000 注解
ACC_ENUM 0x4000 枚举

7.6 EnclosingMethod属性

 EnclosingMethod属性标识当前匿名内所在的外部方法。结构如下:

1
2
3
4
5
6
EnclosingMethod_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 class_index; //指向常量池中的CONSTANT_Class_info型索引
u2 method_index; //指向常量池中的CONSTANT_NameAndType_info型索引
}

  如果不是内部类,则method_index必须为0。

7.7 Synthetic和Deprecated属性

 Synthetic标识方法或字段为编译器自动生成(即不是源码定义的)的。Deprecated标志方法是否过时,只有是和否,没有值。

1
2
3
4
5
Synthetic_attribute {
u2 attribute_name_index; //指向常量池中的CONSTANT_Utf8_info型索引
u4 attribute_length; //值固定为:0x00000000
}
Deprecated同上

7.8 Signature

由于Java的泛型采用擦出法实现,在为了避免类型信息被擦除后导致签名混乱,需要这个属性记录泛型中的相关信息。

1
2
3
4
5
Signature_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 signature_index;
}

7.9 SourceFile和SourceDebugExtension属性

 SourceFile记录生成这个class文件的源码文件名称。SourceDebugExtension存储额外的代码调试信息。

1
2
3
4
5
6
7
8
9
10
11
SourceFile_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 sourcefile_index; //CONSTANT_Utf8_info
}

SourceDebugExtension_attribute {
u2 attribute_name_index;
u4 attribute_length;
u1 debug_extension[attribute_length];
}

7.10 LineNumberTable属性

 描述java源码行号和字节码行号之间的对应关系。非运行必须属性,可以使用javc -g: none XXX.java或者javac -g: lines XXX.java来取消,这样生成的class文件不含LineNumberTable属性,报错时也不会显示行号信息。

1
2
3
4
5
6
7
8
LineNumberTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 line_number_table_length;
{ u2 start_pc; //字节码行号
u2 line_number; //源码行号
} line_number_table[line_number_table_length];
}

7.11 LocalVariableTable和LocalVariableTypeTable属性

 LocalVariableTable用于描述栈帧中局部变量表的变量与java源码中定义的变量之间的关系,不是运行时必须属性。编译时使用javac -g: none XXX.java或者javac -g: vars XXX.java来忽略,这样会导致所有的参数名称丢失,IDE会使用args0、args1来代替原有参数名。不影响程序运行。其结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
LocalVariableTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 local_variable_table_length;
{ u2 start_pc; //局部变量生命周期开始的字节码偏移量
u2 length; //局部变量生命周期开始范围长度
u2 name_index; //局部变量名称,指向CONSTANT_Utf8_info
u2 descriptor_index; //局部变量描述符
u2 index; //局部变量在栈帧的局部变量表中变量槽的位置
} local_variable_table[local_variable_table_length];
}

LocalVariableTypeTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 local_variable_type_table_length;
{ u2 start_pc;
u2 length;
u2 name_index;
u2 signature_index; //记录泛型
u2 index;
} local_variable_type_table[local_variable_type_table_length];
}

 LocalVariableTypeTable是jdk5引入的,作用同LocalVariableTable,记录泛型。

7.12运行时注解相关属性

 JDK5引入了注解,Class文件也增加注解相关的属性。RuntimeVisibleAnnotations、RuntimeInvisibleAnnotations、RuntimeVisibleParameterAnnotations、RuntimeInvisibleParameterAnnotations、RuntimeVisibleTypeAnnotations、RuntimeInvisibleTypeAnnotations这6个注解功能类似。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
RuntimeVisibleAnnotations_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 num_annotations;
annotation annotations[num_annotations];
}

annotation {
u2 type_index; // 指向CONSTAN_Utf8_info的索引,描述注解的字段描述符
u2 num_element_value_pairs; // 注解数量
{ u2 element_name_index; // 该注解的参数
element_value value; // 该注解的值
} element_value_pairs[num_element_value_pairs];
}

 RuntimeVisibleAnnotations记录了类、字段或方法运行时的可见注解,当反射获取注解时,就是通过这个属性获取到的。其他5种注解的结构和它一致,功能类似。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

RuntimeInvisibleAnnotations_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 num_annotations;
annotation annotations[num_annotations];
}

RuntimeVisibleParameterAnnotations_attribute {
u2 attribute_name_index;
u4 attribute_length;
u1 num_parameters;
{ u2 num_annotations;
annotation annotations[num_annotations];
} parameter_annotations[num_parameters];
}

RuntimeInvisibleParameterAnnotations_attribute {
u2 attribute_name_index;
u4 attribute_length;
u1 num_parameters;
{ u2 num_annotations;
annotation annotations[num_annotations];
} parameter_annotations[num_parameters];
}

RuntimeVisibleTypeAnnotations_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 num_annotations;
type_annotation annotations[num_annotations];
}

RuntimeInvisibleTypeAnnotations_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 num_annotations;
type_annotation annotations[num_annotations];
}

AnnotationDefault_attribute {
u2 attribute_name_index;
u4 attribute_length;
element_value default_value;
}

 AnnotationDefault记录注解中的默认值,也就是value参数。

7.13BootstrapMethods属性

 JDK7新增属性,用于保存invokeddynamic指令引用的引导方法限定类。如果类文件结构的常量池中出现过CONSTANT_InvokeDynamic_info常量,则类文件必须存在BoostrapMethod属性,最多一个。结构如下:

1
2
3
4
5
6
7
8
9
BootstrapMethods_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 num_bootstrap_methods;
{ u2 bootstrap_method_ref; //指向CONSTANT_MethodHandle_info类型常量
u2 num_bootstrap_arguments;
u2 bootstrap_arguments[num_bootstrap_arguments];//有点类似链表,一个接一个
} bootstrap_methods[num_bootstrap_methods];
}

7.14模块相关属性

 主要是是为了支持JDK9引入的模块系统,用得少不看了,详情查看官网。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
Module_attribute {
u2 attribute_name_index;
u4 attribute_length;

u2 module_name_index;
u2 module_flags;
u2 module_version_index;

u2 requires_count;
{ u2 requires_index;
u2 requires_flags;
u2 requires_version_index;
} requires[requires_count];

u2 exports_count;
{ u2 exports_index;
u2 exports_flags;
u2 exports_to_count;
u2 exports_to_index[exports_to_count];
} exports[exports_count];

u2 opens_count;
{ u2 opens_index;
u2 opens_flags;
u2 opens_to_count;
u2 opens_to_index[opens_to_count];
} opens[opens_count];

u2 uses_count;
u2 uses_index[uses_count];

u2 provides_count;
{ u2 provides_index;
u2 provides_with_count;
u2 provides_with_index[provides_with_count];
} provides[provides_count];
}


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!