深入学习JVM之Class文件二进制解析
摘要
要深入学习JVM知识, 了解Class文件结构是必不可少的. 这个基础知识是后面了解JVM运行原理与内部细节不可或缺的前提条件. 否则学到的JVM知识都是趋于理论. 难以融会贯通, 也难对以后的JAVA程序调优有实用性的指导. 最终使理论知识和实际工程脱节: 理论一套一套, 实践却好像是另外一回事.因此有必要对
Class
文件进行字节层面的剖析. 以加深JVM在编译层面与运行层面的理解.
目前,大部分的分析
CLASS
文件的文章,大部分把焦点聚焦在了常量池上面.而对于class
文件的其它细节和整体概貌却有缺失. 这篇文章主要是把一个最简单的Class
文件的所有字节都讲清楚. 这样可以对Class
文件的整体结构有更清晰的认识.
class文件格式概述.
参考<深入理解Java虚拟机: jvm高级特性与最佳实践>的定义:
Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地 排列在Class文件之中,中间没有添加任何分隔符,这使得整个Class文件中存储的内容几乎 全部是程序运行的必要数据,没有空隙存在。当遇到需要占用8位字节以上空间的数据项 时,则会按照高位在前的方式分割成若干个8位字节进行存储。
根据Java虚拟机规范的规定,Class文件格式采用一种类似于C语言结构体的伪结构来存 储数据,这种伪结构中只有两种数据类型:无符号数和表,后面的解析都要以这两种数据类 型为基础,所以这里要先介绍这两个概念
我们参考第7版本的JVM虚拟机规范中,对于Class
文件的定义. 这里把一个class文件定义为了一个C语言伪结构题, 这个很重要.后面所有的解析都要以这个结构体为准.
Oracle JVM规范第7版 - Class文件说明
所有JVM相关的最权威的最直接的知识都来自于此, 如果对于JVM有任何的疑问,其实都可以先到此来找答案.
CLASS结构定义
一个Class文件的大体结构是:
- 文件标识: u4 magic , 取值固定为:0xCAFEBABE
- 小版本号: minor_version
- 大版本号:major_version
- 常量池常量数: constant_pool_count
- 常量池 (个数为 count-1): cp_info : constant_pool[constant_pool_count-1]
- 访问标识: access_flags
- 当前类: this_class
- 父类: super_class
- 接口数量: interfaces_count
- 接口数组: interface[interfaces_count]
- 字段数量: fields_count
- 字段数组: field_info fields[fields_count];
- 方法数量: methods_count
- 方法数组: method_info methods[methods_count];
- 属性数量: attributes_count
- 属性数组: attribute_info attributes[attributes_count];
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];
}
Tips: 后面仔细看过Class文件的内容后你会发现. Class文件可以大体分为: 常量(池) + 代码.
所有代码/字段相关的被封装在一个个的结构体里面. 而这些结构体里面需要的一些的附加的描述, 则全部引用常量池里面的常量. 比如类全限定名,父类名称,接口名称,方法名称等.
这里引用了一个国外的教授对CLASS文件的基本解析的PPT , 如果要全面深入的了解CLASS文件的结构可以先看看. 如果想直接实战, 则可以先略过. 后面需要时再回过来看.
源文件:
package train.jvm;
/**
* @author lijianhong Date: 2022/1/24 Time: 1:51 AM
* @version $Id$
*/
public class TestClass {
private Integer age;
private String name;
public String sayHello() {
return "hi,My Name is " + name;
}
}
常量类型type定义:
public class CONSTANT_TYPE {
public static final int CONSTANT_Utf8 = 1; // u1 (tag) , u2 (length) , u1 (bytes)
public static final int CONSTANT_Integer = 3;
public static final int CONSTANT_Float = 4;
public static final int CONSTANT_Long = 5;
public static final int CONSTANT_Double = 6;
public static final int CONSTANT_Class = 7;
public static final int CONSTANT_String = 8;
public static final int CONSTANT_Fieldref = 9;
public static final int CONSTANT_Methodref = 10;
public static final int CONSTANT_InterfaceMethodref = 11;
public static final int CONSTANT_NameAndType = 12; // u1(tag) , u2 (index - 指向该字段或方法 名称常量项 的索引) , u2 (index - 指向该字段或者方法 描述符常量的索引)
public static final int CONSTANT_MethodHandle = 15;
public static final int CONSTANT_MethodType = 16;
public static final int CONSTANT_InvokeDynamic = 18;
private ConstantPool.CPInfo[] pool;
}
属性信息类型定义
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];
}
attribute_info {
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];
}
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
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];
}
CAFE BABE -- 标识: 0xCafeBabe
0000 0034 -- 版本: 小版本:0,大版本:52
# ---------------------------------------------------- 常量池 ----------------------------------------------------------
0021 -- 常量个数: 2*16+1-1 = 32个
#1
0A -- CONSTANT_Methodref_info
00 09 -- 指向声明方法的 类描述符 ( CONSTANT_Class_info ) 的 索引
00 16 -- 指向 名称及类型描述符 ( CONSTANT_NameAndType ) 的 索引
#2
07 -- 类描述符号 ( CONSTANT_Class_info )
0017 -- 指向全限定名常量项的索引 #23 // java/lang/StringBuilder
#3
0A -- CONSTANT_Methodref_info
00 02 -- 指向声明方法的 类描述符 ( CONSTANT_Class_info ) 的 索引
00 16 -- 指向 名称及类型描述符 ( CONSTANT_NameAndType ) 的 索引
#4
08 -- CONSTANT_String_info
0018
#5
0A -- CONSTANT_Methodref_info
00 02 -- 指向声明方法的 类描述符 ( CONSTANT_Class_info ) 的 索引
00 19 -- 指向 名称及类型描述符 ( CONSTANT_NameAndType ) 的 索引
#6
09
0008
001A
#7
0A -- CONSTANT_Methodref_info
00 02 -- 指向声明方法的 类描述符 ( CONSTANT_Class_info ) 的 索引
00 1B -- 指向 名称及类型描述符 ( CONSTANT_NameAndType ) 的 索引
#8
07 -- 类描述符号 ( CONSTANT_Class_info )
001C -- 指向全限定名常量项的索引 #28 // train/jvm/TestClass
#9
07 -- 类描述符号 ( CONSTANT_Class_info )
00 1D -- 指向全限定名常量项的索引 #29 // java/lang/Object
#10
01 -- CONSTANT_Utf8_info
0003 -- length: 3
6167 65 -- String: "age"
#11
01 -- CONSTANT_Utf8_info
0013 -- length: 19 /
4C6A 6176 612F 6C61 6E67 2F49 6E74 6567 6572 3B -- String: "Ljava/lang/Integer;"
#12
01 -- CONSTANT_Utf8_info
0004 -- length: 4
6E61 6D65 -- String: "name"
#13
01 -- CONSTANT_Utf8_info
00 12 -- length: 18
4C 6A61 7661 2F6C 616E 672F 5374 7269 6E67 3B -- String: "Ljava/lang/String;"
#14
01 -- CONSTANT_Utf8_info
0006 -- length: 6
3C69 6E69 743E -- String: "<init>"
#15
01 -- CONSTANT_Utf8_info
00 03 -- length: 3
28 2956 -- String: "()V"
#16
01 -- CONSTANT_Utf8_info
00 04 -- length: 4
43 6F64 65 -- String: "Code"
#17
01 -- CONSTANT_Utf8_info
000F -- length: 15
4C69 6E65 4E75 6D62 6572 5461 626C 65 : "LineNumberTable"
#18
01 -- CONSTANT_Utf8_info
0008 -- length: 8
7361 7948 656C 6C6F : "sayHello"
#19
01 -- CONSTANT_Utf8_info
00 14 -- length: 20
28 294C 6A61 7661 2F6C 616E 672F 5374 7269 6E67 3B : "()Ljava/lang/String;"
#20
01 -- CONSTANT_Utf8_info
000A -- length: 10
536F 7572 6365 4669 6C65 : "SourceFile"
#21
01 -- CONSTANT_Utf8_info
00 0E -- length: 14
54 6573 7443 6C61 7373 2E6A 6176 61 : "TestClass.java"
#22
0C -- CONSTANT_NameAndType_info
000E -- 名称常量项 #14 "<init>"
000F -- 描述符常量项 #15 "()V" => // "<init>":()V
#23
01 -- CONSTANT_Utf8_info
00 17 -- length: 23
6A 6176 612F 6C61 6E67 2F53 7472 696E 6742 7569 6C64 6572 : "java/lang/StringBuilder"
#24
01
00 0E -- length: 14
68 692C 4D79 204E 616D 6520 6973 20 : "hi,My Name is"
#25
0C -- CONSTANT_NameAndType_info
001E -- 名称常量项: #30
001F -- 描述符常量项 #31 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#26
0C -- CONSTANT_NameAndType_info
00 0C -- 名称常量项: #12 // name
00 0D -- 描述符常量项 #13 // Ljava/lang/String;
#27
0C -- CONSTANT_NameAndType_info
0020 -- 名称常量项: #32
0013 -- 描述符常量项 #19
#28
01 -- CONSTANT_Utf8_info
00 13 -- length: 19
74 7261 696E 2F6A 766D 2F54 6573 7443 6C61 7373 : train/jvm/TestClass
#29
01 -- CONSTANT_Utf8_info
00 10 -- length: 16
6A 6176 612F 6C61 6E67 2F4F 626A 6563 74 : java/lang/Object
#30
01 -- CONSTANT_Utf8_info
0006 -- length: 6
6170 7065 6E64
#31
01 -- CONSTANT_Utf8_info
00 2D -- length: 45 : (Ljava/lang/String;)Ljava/lang/StringBuilder;
28 4C6A 6176 612F 6C61 6E67 2F53 7472 696E 673B 294C 6A61 7661 2F6C 616E 672F 5374 7269 6E67 4275 696C 6465 723B
#32
01 -- CONSTANT_Utf8_info
00 08 -- length: 8
74 6F53 7472 696E 67 : toString
# --------------------------------------- 访问标识 ----------------------------------------------------------------------
# access flag
00 21 -- 0000 0000 0010 0001
^ ^
| |____ ACCESS_PUBLIC
|__________ ACCESS_SUPER : 是否使用invoke special字节码的新语意. 1.0.2之后编译的类这个标识需要为真
# ---------------------------------------- 类索引 -----------------------------------------------------------------------
# this_class
00 08 -- #8/CONSTANT_Class_info -> #28/CONSTANT_Utf8_info : "train/jvm/TestClass"
# ---------------------------------------- 父类索引 ---------------------------------------------------------------------
# super_class
00 09 -- #9/CONSTANT_Class_info -> #29/CONSTANT_Utf8_info : "java/lang/Object"
# ---------------------------------------- 接口索引 ---------------------------------------------------------------------
# interface_counts
00 00 -- 有零个接口
----------------------------------------- 字段表集合 --------------------------------------------------------------------
> 用于描述接口或者类中声明的变量. 字段包含: 类级变量 / 实例级变量.
# field_counts
00 02 -- 有两个字段
# private "Ljava/lang/Integer;" : "age"
00 02 -- access_flags 0x02: ACC_PRIVATE/字段是否为private
00 0A -- name_index: #10 --> CONSTANT_Utf8_info: "age"
00 0B -- descriptor_index:#11 --> CONSTANT_Utf8_info: "Ljava/lang/Integer;"
00 00 -- attributes_count: --> 0
#
00 02 -- access_flags 0x02: ACC_PRIVATE/字段是否为private
00 0C -- name_index: #12 --> CONSTANT_Utf8_info: "name"
00 0D -- descriptor_index:#13 --> CONSTANT_Utf8_info: "Ljava/lang/String;"
00 00 -- attributes_count: --> 0
----------------------------------------- 方法表集合 --------------------------------------------------------------------
00 02 -- 有两个字段
// 方法信息
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
// 属性信息
attribute_info {
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];
}
# method_info
00 01 -- access_flags 0x01: ACC_PUBLIC/字段是否为public
00 0E -- name_index: #14 --> CONSTANT_Utf8_info: "<init>"
00 0F -- descriptor_index:#15 --> CONSTANT_Utf8_info: "()V"
00 01 -- attributes_count:1
# attribute_info
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];
}
00 10 -- attribute_name_index: #16 --> CONSTANT_Utf8_info: "code"
00 0000 1D-- attribute_length --> 属性长度: 29
00 01 -- max_stack: 栈深度为1
00 01 -- max_locals: 最大局部变量
00 0000 05-- code_length: 5
-- ---------> 代码 <---------
2A // aload_0 , 从局部变量表加载一个 reference 类型值到操作数栈中
B700 // 0xb7 invokespecial 调用超类构造方法,实例初始化方法,私有方法。
// 请注意,所有使用 invokespecial 指令调用的方法都需要 this 作为第一个参数,保存在 第一个局部变量之中。
01 // aconst_null , 将 null 推送至栈顶。
B1 // return 从当前方法返回 void。
0000 -- exception_table_length
0001 -- attributes_count: 1
// LineNumberTable 属性是可选变长属性,位于 Code(§4.7.3)结构的属性表。
// 它被调试 器用于确定源文件中行号表示的内容在 Java 虚拟机的 code[]数组中对应的部分。
// 在 Code 属性 的属性表中,LineNumberTable 属性可以按照任意顺序出现,此外,\
// 多个 LineNumberTable 属性可以共同表示一个行号在源文件中表示的内容,
// 即 LineNumberTable 属性不需要与源文件 的行一一对应。
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];
}
0011 -- #17 --> UTF8: LineNumberTable
0000 0006 -- attribute_length: 6
0001 0000 0007
00 01 -- access_flags 0x01: ACC_PUBLIC/字段是否为public
00 12 -- name_index: #18 --> CONSTANT_Utf8_info: "sayHello"
00 13 -- descriptor_index:#19 --> CONSTANT_Utf8_info: "()Ljava/lang/String;"
00 01 -- attributes_account:1
00 10 -- attribute_name_index: #16 --> CONSTANT_Utf8_info: "code"
0000 002F -- attribute_length --> 属性长度: 47
-- ---------> 代码 <---------
0002 -- max_stack: 栈深度为 2
0001 -- max_locals: 最大局部变量 1
0000 0017 -- code_length: 23
BB // new , 创建一个对象,并将其引用值压入栈顶。
00
02 // iconst_m1 , 将 int 型-1 推送至栈顶
59 // dup , 复制栈顶数值并将复制值压入栈顶
B7 // invokespecial , 调用超类构造方法,实例初始化方法,私有方法。
00
03 // 0x03 iconst_0 , 将 int 型 0 推送至栈顶。
12 04 // 0x12 ldc , 将 int,float 或 String 型常量值从常量池中推送至栈顶 , 这里是把 #4号推送到栈顶. (#4: hi,My Name is)
B6 0005 // 0xb6 invokevirtual , 调用实例方法。 invoke #5 (实际为append方法): java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
2A // aload_0 , 从局部变量表加载一个 reference 类型值到操作数栈中 ; 将第一个引用类型局部变量推送至栈顶
B4 00 // 0xb4 getfield , 获取指定类的实例域,并将其值压入栈顶。
06 B600 05B6 0007
B0 // areturn 从当前方法返回对象引用。
00 00 -- exception_table_length: 0
00 01 -- attributes_count: 1 代码属性中的内部属性个数,1
00 11 -- #17 --> UTF8: LineNumberTable
00 0000 06-- attribute_length: 6
00 0100 0000 0E
----------------------------------------- 属性表集合 --------------------------------------------------------------------
00 01 -- 属性表 信息 Count: 1
attribute_info {
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];
}
00 14 -- attribute_name_index: #20 -> "SourceFile"
00 0000 02-- attribute_length
SourceFile_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 sourcefile_index;
}
00 15 -- sourcefile_index: #21 --> "TestClass.java"
javap 编译结果
Classfile /Users/lijianhong/IdeaProject/tec_note/train/src/main/java/train/jvm/TestClass.class
Last modified Jan 24, 2022; size 521 bytes
MD5 checksum 71f6dbdf29861390494d8c565872f1dd
Compiled from "TestClass.java"
public class train.jvm.TestClass
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #9.#22 // java/lang/Object."<init>":()V
#2 = Class #23 // java/lang/StringBuilder
#3 = Methodref #2.#22 // java/lang/StringBuilder."<init>":()V
#4 = String #24 // hi,My Name is
#5 = Methodref #2.#25 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#6 = Fieldref #8.#26 // train/jvm/TestClass.name:Ljava/lang/String;
#7 = Methodref #2.#27 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#8 = Class #28 // train/jvm/TestClass
#9 = Class #29 // java/lang/Object
#10 = Utf8 age
#11 = Utf8 Ljava/lang/Integer;
#12 = Utf8 name
#13 = Utf8 Ljava/lang/String;
#14 = Utf8 <init>
#15 = Utf8 ()V
#16 = Utf8 Code
#17 = Utf8 LineNumberTable
#18 = Utf8 sayHello
#19 = Utf8 ()Ljava/lang/String;
#20 = Utf8 SourceFile
#21 = Utf8 TestClass.java
#22 = NameAndType #14:#15 // "<init>":()V
#23 = Utf8 java/lang/StringBuilder
#24 = Utf8 hi,My Name is
#25 = NameAndType #30:#31 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#26 = NameAndType #12:#13 // name:Ljava/lang/String;
#27 = NameAndType #32:#19 // toString:()Ljava/lang/String;
#28 = Utf8 train/jvm/TestClass
#29 = Utf8 java/lang/Object
#30 = Utf8 append
#31 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#32 = Utf8 toString
{
public train.jvm.TestClass();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 7: 0
public java.lang.String sayHello();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: new #2 // class java/lang/StringBuilder
3: dup
4: invokespecial #3 // Method java/lang/StringBuilder."<init>":()V
7: ldc #4 // String hi,My Name is
9: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
12: aload_0
13: getfield #6 // Field name:Ljava/lang/String;
16: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
22: areturn
LineNumberTable:
line 14: 0
}
SourceFile: "TestClass.java"