image-20260311163121976

类文件结构

1
2
3
4
5
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World");
}
}

他的.class文件为:

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
0000000 ca fe ba be 00 00 00 34 00 23 0a 00 06 00 15 09 
0000020 00 16 00 17 08 00 18 0a 00 19 00 1a 07 00 1b 07
0000040 00 1c 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29
0000060 56 01 00 04 43 6f 64 65 01 00 0f 4c 69 6e 65 4e
0000100 75 6d 62 65 72 54 61 62 6c 65 01 00 12 4c 6f 63
0000120 61 6c 56 61 72 69 61 62 6c 65 54 61 62 6c 65 01
0000140 00 04 74 68 69 73 01 00 1d 4c 63 6e 2f 69 74 63
0000160 61 73 74 2f 6a 76 6d 2f 74 35 2f 48 65 6c 6c 6f
0000200 57 6f 72 6c 64 3b 01 00 04 6d 61 69 6e 01 00 16
0000220 28 5b 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72
0000240 69 6e 67 3b 29 56 01 00 04 61 72 67 73 01 00 13
0000260 5b 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69
0000300 6e 67 3b 01 00 10 4d 65 74 68 6f 64 50 61 72 61
0000320 6d 65 74 65 72 73 01 00 0a 53 6f 75 72 63 65 46
0000340 69 6c 65 01 00 0f 48 65 6c 6c 6f 57 6f 72 6c 64
0000360 2e 6a 61 76 61 0c 00 07 00 08 07 00 1d 0c 00 1e
0000400 00 1f 01 00 0b 68 65 6c 6c 6f 20 77 6f 72 6c 64
0000420 07 00 20 0c 00 21 00 22 01 00 1b 63 6e 2f 69 74
0000440 63 61 73 74 2f 6a 76 6d 2f 74 35 2f 48 65 6c 6c
0000460 6f 57 6f 72 6c 64 01 00 10 6a 61 76 61 2f 6c 61
0000500 6e 67 2f 4f 62 6a 65 63 74 01 00 10 6a 61 76 61
0000520 2f 6c 61 6e 67 2f 53 79 73 74 65 6d 01 00 03 6f
0000540 75 74 01 00 15 4c 6a 61 76 61 2f 69 6f 2f 50 72
0000560 69 6e 74 53 74 72 65 61 6d 3b 01 00 13 6a 61 76
0000600 61 2f 69 6f 2f 50 72 69 6e 74 53 74 72 65 61 6d
0000620 01 00 07 70 72 69 6e 74 6c 6e 01 00 15 28 4c 6a
0000640 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b
0000660 29 56 00 21 00 05 00 06 00 00 00 00 00 02 00 01
0000700 00 07 00 08 00 01 00 09 00 00 00 2f 00 01 00 01
0000720 00 00 00 05 2a b7 00 01 b1 00 00 00 02 00 0a 00
0000740 00 00 06 00 01 00 00 00 04 00 0b 00 00 00 0c 00
0000760 01 00 00 00 05 00 0c 00 0d 00 00 00 09 00 0e 00
0001000 0f 00 02 00 09 00 00 00 37 00 02 00 01 00 00 00
0001020 09 b2 00 02 12 03 b6 00 04 b1 00 00 00 02 00 0a
0001040 00 00 00 0a 00 02 00 00 00 06 00 08 00 07 00 0b
0001060 00 00 00 0c 00 01 00 00 00 09 00 10 00 11 00 00
0001100 00 12 00 00 00 05 01 00 10 00 00 00 01 00 13 00
0001120 00 00 02 00 14

根据 JVM 规范,类文件结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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];
}

魔数

0~3 字节,表示是否是 class 类型的文件。

0000000 ca fe ba be 00 00 00 34 00 23 0a 00 06 00 15 09

cafebabe代表是Java类型

版本

4~7 字节,表示类的版本。

0000000 ca fe ba be 00 00 00 34 00 23 0a 00 06 00 15 09

34 是十六进制,对应十进制 52,表示类的版本是 Java 8。

常量池

常量类型 (Constant Type) 标志值 (Value) 描述 (Description)
CONSTANT_Utf8 1 UTF-8 编码的字符串(如类名、方法名)
CONSTANT_Integer 3 整型字面量
CONSTANT_Float 4 浮点型字面量
CONSTANT_Long 5 长整型字面量(占 2 个常量池槽位)
CONSTANT_Double 6 双精度浮点型字面量(占 2 个常量池槽位)
CONSTANT_Class 7 类或接口的符号引用
CONSTANT_String 8 字符串类型字面量
CONSTANT_Fieldref 9 字段的符号引用
CONSTANT_Methodref 10 类中方法的符号引用
CONSTANT_InterfaceMethodref 11 接口中方法的符号引用
CONSTANT_NameAndType 12 字段或方法的名称和类型描述符
CONSTANT_MethodHandle 15 方法句柄(用于支持动态语言)
CONSTANT_MethodType 16 方法类型(用于支持动态语言)
CONSTANT_InvokeDynamic 18 动态方法调用点(Lambda 表达式核心)

8~9 字节,表示常量池长度:

0000000 ca fe ba be 00 00 00 34 00 23 0a 00 06 00 15 09

23 十进制对应 35,表示常量池有 #1 ~ #34 项,#0 项不计入,也没有值。

#1

#1 项 0a 对应十进制 10,根据上表查询得知,表示 CONSTANT_Methodref,即方法信息。00 06 和 00 15(21) 表示它引用了常量池中 #6 和 #21 项来获取这个方法的所属类和方法名:

0000000 ca fe ba be 00 00 00 34 00 23 0a 00 06 00 15 09

00 06 对应第 #6 项,表示 Class 信息,然后它又引用了第 #28 项,指明具体的 Class 是 java/lang/Object。

00 15 对应第 #21 项,表示方法名、参数类型和返回值类型。它分别引用 #7 项表名方法名是 ,即构造方法;然后又引用 #8 项,指定方法描述符为 ()V,即无参无返回值。

然后再一项一项查下去就行了

访问标识与继承信息

标志名称 (Flag Name) 标志值 (Value) 中文含义 (Interpretation)
ACC_PUBLIC 0x0001 标识为 public 类型,可以被包外访问。
ACC_FINAL 0x0010 标识为 final 类型,不允许有子类(禁止继承)。
ACC_SUPER 0x0020 使用新的 invokespecial 语义(现代编译器的必选项)。
ACC_INTERFACE 0x0200 标识这是一个接口,而不是一个普通的类。
ACC_ABSTRACT 0x0400 标识为 abstract 类型,不能被实例化。
ACC_SYNTHETIC 0x1000 标识该类由编译器自动生成,源码中并不存在。
ACC_ANNOTATION 0x2000 标识这是一个注解类型(Annotation)。
ACC_ENUM 0x4000 标识这是一个枚举类型(Enum)。

00 21 (由上表中的 0x0001 和 0x0020 相加获得)表示该 class 是一个公共的类:

0000660 29 56 00 21 00 05 00 06 00 00 00 00 00 02 00 01

00 05 表示根据常量池中的 #5 项找到 本类 的全限定名:

0000660 29 56 00 21 00 05 00 06 00 00 00 00 00 02 00 01

00 06 表示根据常量池中的 #6 找到 父类 的全限定名:

0000660 29 56 00 21 00 05 00 06 00 00 00 00 00 02 00 01

00 00 表示该类实现的接口数量,此处为 0:

0000660 29 56 00 21 00 05 00 06 00 00 00 00 00 02 00 01

Field 信息

00 00 表示成员变量数量,此处为 0:

0000660 29 56 00 21 00 05 00 06 00 00 00 00 00 02 00 01

标识字符 (FieldType) 对应类型 (Type) 中文解释 (Interpretation)
B byte 有符号字节型
C char Unicode 字符(使用 UTF-16 编码)
D double 双精度浮点型
F float 单精度浮点型
I int 整型
J long 长整型(因为 L 给了对象,所以用 J)
L 类名 ; reference 对象引用类型(以 L 开头,分号 ; 结尾)
S short 有符号短整型
Z boolean 布尔型(true 或 false)
[ reference 数组维度(一个 [ 代表一维数组)

Method 信息

00 02 表示方法数量,本类为 2(默认无参构造方法与 main 方法):

0000660 29 56 00 21 00 05 00 06 00 00 00 00 00 02 00 01
一个方法由访问修饰符、名称、参数描述、方法属性数量、方法属性组成。

eg:构造方法

1
2
3
4
5
0000660 29 56 00 21 00 05 00 06 00 00 00 00 00 02 00 01
0000700 00 07 00 08 00 01 00 09 00 00 00 2f 00 01 00 01
0000720 00 00 00 05 2a b7 00 01 b1 00 00 00 02 00 0a 00
0000740 00 00 06 00 01 00 00 00 04 00 0b 00 00 00 0c 00
0000760 01 00 00 00 05 00 0c 00 0d 00 00 00 09 00 0e 00

附加属性

0001100 00 12 00 00 00 05 01 00 10 00 00 00 01 00 13 00
0001120 00 00 02 00 14

00 01 表示附加属性数量
00 13 表示引用了常量池 #19 项,即 SourceFile,表示字节码文件对应的 Java 源文件名称
00 00 00 02 表示此属性长度
00 14 表示引用了常量池 #20 项,即 HelloWorld.java

字节码指令

eg:

对应字节码指令:

b2 00 02 12 03 b6 00 04 b1

查询 JVM 规范得知,b2 对应 getstatic,用于加载静态变量
00 02 引用常量池中 #2 项,表示 getstatic 需要加载的静态变量信息,简单来说是 System.out
12 对应 ldc(load constant),用于加载参数
03 引用常量池中 #3 项,即字符串常量 Hello World
b6 对应 invokevirtual,预备调用成员方法
00 04 引用常量池中 #4 项,即 println 方法
b1 表示返回

javap

Oracle 提供了 javap 工具来反编译 class 文件:

1
javap -v HelloWorld.class

-v 参数表示输出 class 文件的详细信息。

图解方法执行流程

示例代码:

1
2
3
4
5
6
7
8
public class Demo1 {
public static void main(String[] args) {
int a = 10;
int b = Short.MAX_VALUE + 1;
int c = a + b;
System.out.println(c);
}
}

对于 short 范围内的整数不会存入运行时常量池中,而是与字节码指令一起存放。short 的最大值是 32767,加一后大于最大值,因此 32768 会被存放到运行时常量池中。

方法字节码载入方法区

image-20260311174148350

main 线程开始运行,分配栈帧内存

image-20260311174319405

通过 javap 命令查看的字节码文件中存在:

1
2
3
4
5
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4

stack=2 表示操作数栈的深度是 2
locals=4 表示局部变量表有 4 个槽位

执行引擎开始执行字节码

bipush 10 表示将一个 byte 压入操作数栈(由于操作数栈的宽度占 4 个字节,压入内容不足 4 个字节时,会补齐 4 个字节),类似的指令还有:

sipush 将一个 short 压入操作数栈(其长度会补齐 4 个字节)
ldc 将一个 int 压入操作数栈
ldc2_w 将一个 long 压入操作数栈(因为 long 是 8 个字节,需要分两次压入)
小数字和字节码指令存放在一起,超过 short 范围的数字会存入常量池

image-20260311174459193

istore_1 表示弹出操作数栈栈顶数据,存入局部变量表的 slot 1:

image-20260311174542189

ldc #3 表示从运行时常量池中加载 #3 数据到操作数栈。

image-20260311174627163

istore_2 表示弹出操作数栈栈顶数据,存入局部变量表的 slot 2:

image-20260311174744045

接着从局部变量表里读取参与加法运算的两个数:

iload_1 表示从局部变量表 slot 1 里读取数据
iload_2 表示从局部变量表 slot 2 里读取数据

image-20260311174840402

然后执行 iadd 弹出堆操作数栈里的两个整型数据进行相加:

image-20260311174911049

再把运算结果压入操作数栈顶:

image-20260311174932025

istore_3 再弹出操作数栈栈顶数据,存入局部变量表的 slot 3:

image-20260311175014554

getstatic #4 从运行时常量池中获取成员变量 System.out 的引用,然后把该对象的 引用 添加进操作数栈中:

image-20260311175111686

在调用 println 方法前,需要先加载所需的参数,使用 iload_3 从局部变量表 slot 3 里读取数据:

image-20260311175207053

使用 invokevirtual #5 调用方法打印数据:

找到运行时常量池 #5 项
定位到方法区 java/io/PrintStream.println:(I)V 方法
生成新的栈帧(分配 locals、stack 等)
传递参数,执行新栈帧中的字节码

image-20260311175242922

目标方法执行完毕后,弹出栈帧。

清除 main 操作数栈的内容:

image-20260311175335789

完成 main 方法调用后,弹出 main 栈帧,程序结束。

分析a++

1
2
3
4
5
6
7
8
9
10
public class Demo1_2 {
public static void main(String[] args) {
int a = 10;
int b = a++ + ++a + a--;
// 11
System.out.println(a);
// 34
System.out.println(b);
}
}

字节码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: bipush 10
2: istore_1
3: iload_1
4: iinc 1, 1
7: iinc 1, 1
10: iload_1
11: iadd
12: iload_1
13: iinc 1, -1
16: iadd
17: istore_2
18: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
21: iload_1
22: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
25: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
28: iload_2
29: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
32: return

分析:

iinc 指令是直接在局部变量 slot 上进行运算的
a++ 和 ++a 的区别是先执行 iload 还是先执行 iinc
a++ 会被分解为两条字节码指令:

iload_1
iinc 1,1

image-20260312142749881

++a 也会被分解为两条字节码指令,内容也与 a++ 一样,但顺序不同:

iinc 1,1

iload_1

image-20260312143130428

image-20260312143317358

image-20260312143441403

image-20260312143508399

image-20260312143531231

image-20260312143549039

条件判断指令

指令 助记符 含义
0x99 ifeq 判断是否 $== 0$
0x9a ifne 判断是否 $!= 0$
0x9b iflt 判断是否 $< 0$
0x9c ifge 判断是否 $>= 0$
0x9d ifgt 判断是否 $> 0$
0x9e ifle 判断是否 $<= 0$
0x9f if_icmpeq 两个 int 是否 $==$
0xa0 if_icmpne 两个 int 是否 $!=$
0xa1 if_icmplt 两个 int 是否 $<$
0xa2 if_icmpge 两个 int 是否 $>=$
0xa3 if_icmpgt 两个 int 是否 $>$
0xa4 if_icmple 两个 int 是否 $<=$
0xa5 if_acmpeq 两个引用是否 $==$
0xa6 if_acmpne 两个引用是否 $!=$