字节码文件结构
一篇关于Java字节码文件结构的学习笔记。
Chapter 4. The class File Format (oracle.com) (opens new window)
Java的字节码文件格式规定如下:
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
Class文件由无符号数和表组成:
- 在JVM的的规范中,u1、u2、u4分别表示1、2、4字节的无符号数
- 表是由多个无符号数或者其他表作为数据项的复杂数据类型,都习惯以
_info
结尾
下面以这个class文件为例:
interface TestInterface {
void hello();
}
public class TestClass implements TestInterface {
private int id;
public TestClass() {
this.id = 3;
}
@Override
public void hello() {
System.out.println("Hello" + id);
}
}
# Magic
Magic(魔数)是class文件的标识,其有固定值 0xCAFEBABE
,JVM加载class文件的时候会先加载4字节的数据( u4 magic
)来判断该文件是不是字节码文件。
# minor/major_version
minor_version
和major_version
两部分组成了class文件的版本号,两者分别表示副版本号和主版本号,我们常说的Java1.8、Java11表示的就是主版本号,下表是版本号的对应关系:
JDK版本 | 十进制 | 十六进制 | 发布时间 |
---|---|---|---|
JDK1.1 | 45 | 2D | 1996-05 |
JDK1.2 | 46 | 2E | 1998-12 |
JDK1.3 | 47 | 2F | 2000-05 |
JDK1.4 | 48 | 30 | 2002-02 |
JDK1.5 | 49 | 31 | 2004-09 |
JDK1.6 | 50 | 32 | 2006-12 |
JDK1.7 | 51 | 33 | 2011-07 |
JDK1.8 | 52 | 34 | 2014-03 |
Java9 | 53 | 35 | 2017-09 |
Java10 | 54 | 36 | 2018-03 |
Java11 | 55 | 37 | 2018-09 |
Java12 | 56 | 38 | 2019-03 |
Java13 | 57 | 39 | 2019-09 |
Java14 | 58 | 3A | 2020-03 |
Java15 | 59 | 3B | 2020-09 |
# constant_pool_count
常量池计数器 u2 constant_pool_count
)表示的是常量池中的数量,其值为常量池中的数量 + 1
,需要特别注意的是long
和double
类型的常量池对象占两个常量位,并且常量池计数是从1开始的。
0X0036也就是54,表示常量池中有53个元素。
# constant_pool
常量池(constant_pool),cp_info constant_pool[constant_pool_count-1]
是一种表结构,cp_info
表示的是常量池对象。
常量池中主要存放字面量和符号引用:
- 字面量:文本字符串、被声明为
final
的常量 - 符号引用:类和接口的全限定名、字段的名称和描述符、方法的名称和描述符
常量池中的每一个元素都是一个表,表的类型共有14种,它们都具有一个共同的特点:表开始的部分都是一个u1
的标志位。
下图为常量池中14中数据类型的总表:
常量池中第一个一个常量的标志为0A
,也就是10,表示该常量为CONSTANT_Methodref_info
,为类中方法的符号引用,按照上表,可以读出指向方法的类描述符的索引位置为0x0007
,指向名称及类型描述符的索引位置为0x0015
······
使用jclasslib插件可以快速查看常量池中的常量信息:
# access_flags
访问标志(u2 access_flags
)用于识别一些类或者接口层次的访问信息。
标志名 | 十六进制值 | 描述 |
---|---|---|
ACC_PUBLIC | 0x0001 | 声明为public |
ACC_FINAL | 0x0010 | 声明为final |
ACC_SUPER | 0x0020 | 废弃/仅JDK1.0.2前使用,1.0.2之后都为真 |
ACC_INTERFACE | 0x0200 | 声明为接口 |
ACC_ABSTRACT | 0x0400 | 声明为abstract |
ACC_SYNTHETIC | 0x1000 | 声明为synthetic,表示该class文件并非由Java源代码所生成 |
ACC_ANNOTATION | 0x2000 | 标识注解类型 |
ACC_ENUM | 0x4000 | 标识枚举类型 |
对于例子TestClass
来说,它的标志应该是ACC_PUBLIC
和ACC_SUPER
,所以access_flags
的值为0x0001 | 0x0020 = 0x0021
。
# this_class
u2 this_class
表示的是当前类在常量池中的索引位置(CONSTANT_Class_info
)。
# super_class
u2 super_class
表示的是当前类的父类在常量池中的索引位置。
# interfaces_count
u2 interfaces_count
表示当前类实现的接口数量。
# interfaces[interfaces_count]
u2 interfaces[interfaces_count]
表示所有接口在常量池中的索引位置。
# fields_count
u2 fields_count
表示类中成员变量的数量。
# fields[fields_count]
field_info fields[fields_count]
是成员变量表(字段表)信息,字段表的结构如下:
field_info {
u2 access_flags; //成员变量访问标志
u2 name_index; //成员变量名称在常量池中的索引
u2 descriptor_index; //成员变量的描述符在常量池中的索引
u2 attributes_count; //成员变量属性数量
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_FIANL一起使用 ACC_TRANSIENT 0x0080 在序列化中被忽略的字段 ACC_SYNTHETIC 0x1000 由编译器产生,不存在于源代码中 ACC_ENUM 0x4000 enum
- 0x0002表示字段的访问标志为
ACC_PRIVATE
- 0x0009表示变量名称在常量池中的索引位置为9
- 0x000A表示变量描述符在常量池中的索引位置为10
- 0x0000表示属性数量为0
# methods_count
u2 methods_count
表示类的方法数量。
# methods[methods_count]
method_info methods[methods_count]
是方法表,结构和字段表是一样的。
method_info {
u2 access_flags; //方法访问标志
u2 name_index; //方法名称在常量池中的索引
u2 descriptor_index; //方法的描述符在常量池中的索引
u2 attributes_count; //方法属性数量
attribute_info attributes[attributes_count]; //方法的属性信息
}
attribute_info attributes[attributes_count]
是一个很复杂的存储结构,存储着各种属性信息:
attribute_info {
u2 attribute_name_index; //属性名称在常量池中的索引
u4 attribute_length; //属性长度
u1 info[attribute_length]; //属性信息,不同属性的结构不同
}
值得注意的是,方法代码逻辑是放在code
属性中的,是以Java虚拟机指令的形式存储的。
code属性的结构:
类型 名称 含义 u2 attribute_name_index 属性名称索引 u4 attribute_length 属性长度 u2 max_stack 操作数栈深度的最大值 u2 max_locals 局部变量表所需的存储空间 u4 code_length 字节码长度 u1 code[code_length] 存储字节码指令的一系列字节流 u2 exception_table_length 异常表长度 exception_info exception_table 异常表 u2 attributes_count 属性长度 attribute_info attributes[attributes_count] 属性表
接着来分析code
属性:
- 000D:code属性在常量池中的索引为13
- 0000 003C:属性长度为60字节
- 0002:max_stack为2
- 0001:max_locals为1
- 0000 000A:code_length为10,表示还有10个字节来存储指令
- 2AB7 0001 2A06 B500 02B1:存储JVM指令的字节流
- 0000:没有异常抛出
- 0002:表示
code
属性还有2个属性
code
属性中主要包含的属性为:LineNumberTable
和LocalVariableTable
。
LineNumberTable
的结构如下:
LineNumberTable_attribute {
u2 attribute_name_index; //属性名称索引
u4 attribute_length; //属性长度
u2 line_number_table_length;
{ u2 start_pc; //字节码行号
u2 line_number; //java源码行号
} line_number_table[line_number_table_length];
}
000E:属性索引位置在常量池中的索引位置为14
0000 000E:属性长度为14个字节
0003:line_number_table_length为3
0000 000A:前两个字节表示字节码行号,后两个字节表示对应的Java代码行号
······
具体属性结构可以参考:Chapter 4. The class File Format (oracle.com) (opens new window)
# attributes_count
u2 attributes_count
表示当前class文件的属性表的元素个数。
# attributes[attributes_count]
Java15中的属性表:
属性名称 章节 ConstantValue Attribute §4.7.2 Code Attribute §4.7.3 StackMapTable Attribute §4.7.4 Exceptions Attribute §4.7.5 InnerClasses Attribute §4.7.6 EnclosingMethod Attribute §4.7.7 Synthetic Attribute §4.7.8 Signature Attribute §4.7.9 SourceFile Attribute §4.7.10 SourceDebugExtension Attribute §4.7.11 LineNumberTable Attribute §4.7.12 LocalVariableTable Attribute §4.7.13 LocalVariableTypeTable Attribute §4.7.14 Deprecated Attribute §4.7.15 RuntimeVisibleAnnotations Attribute §4.7.16 RuntimeInvisibleAnnotations Attribute §4.7.17 RuntimeVisibleParameterAnnotations Attribute §4.7.18 RuntimeInvisibleParameterAnnotations Attribute §4.7.19 RuntimeVisibleTypeAnnotations Attribute §4.7.20 RuntimeInvisibleTypeAnnotations Attribute §4.7.21 AnnotationDefault Attribute §4.7.22 BootstrapMethods Attribute §4.7.23 MethodParameters Attribute §4.7.24 Module Attribute §4.7.25 ModulePackages Attribute §4.7.26 ModuleMainClass Attribute §4.7.27 NestHost Attribute §4.7.28 NestMembers Attribute §4.7.29
属性表是动态的,每一种属性都有自己独有的数据结构,在读取到属性名称之后还要根据属性的不同类型来解析不同属性表中的值。
- 02
- CommonsBeanUtils04-19
- 03
- 基于Tomcat全局存储进行回显04-16