1. 字节码基础概念

  • 什么是字节码:Java 源代码(.java)编译后生成的中间代码(.class),由 JVM 执行。
  • 文件结构:遵循 JVM 规范,包含魔数、版本号、常量池、类信息、方法表等。
  • 指令集:基于栈的虚拟机指令,例如 iload(加载整型)、invokevirtual(调用方法)等。

2. 工具准备

查看字节码的常用工具

  • javap(JDK 自带):

    1
    javap -c -v YourClass.class  # 反编译并显示详细字节码
    • -c:输出指令码。
    • -v:显示附加信息(常量池、行号表等)。
  • IDE 插件

    • IntelliJ IDEA:安装 Bytecode Viewer 插件(右键类文件 → View → Show Bytecode)。
    • Eclipse:使用 Bytecode Outline 插件。
  • 图形化工具

    • JClassLib:可视化分析 .class 文件。
    • JD-GUI:反编译为近似 Java 代码(但非原始字节码)。

3. 字节码文件结构

关键组成部分

  1. 魔数(Magic Number):前 4 字节为 0xCAFEBABE,标识为 Java 类文件。
  2. 版本号:主版本(Major Version)和次版本(Minor Version),例如 Java 8 的主版本号为 52
  3. 常量池(Constant Pool)
    • 存储类名、方法名、字符串字面量等符号引用。
    • 通过索引(如 #1, #2)在指令中引用。
  4. 访问标志(Access Flags):类的修饰符(public, final 等)。
  5. 类信息:父类、接口列表。
  6. 字段表(Fields):类中定义的字段。
  7. 方法表(Methods):每个方法的字节码、异常表、本地变量表等。
  8. 属性表(Attributes):附加信息(如源码文件名、行号表)。

4. 理解字节码指令

常见指令示例

  • 加载/存储
    • aload_0:加载 this 到操作数栈。
    • iload_1:加载局部变量槽 1 的整型值。
  • 算术运算
    • iadd:整型加法。
    • fsub:浮点减法。
  • 方法调用
    • invokevirtual:调用实例方法。
    • invokestatic:调用静态方法。
  • 控制流
    • ifeq:如果栈顶值为 0,跳转。
    • goto:无条件跳转。
  • 对象操作
    • new:创建对象。
    • putfield:设置对象字段的值。

Java 字节码文件的分析和对应的 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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// class version 61.0 (61)
// access flags 0x21
public class com/mozi/Main {

// compiled from: Main.java

// access flags 0x1
public <init>()V
L0
LINENUMBER 14 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L1
LOCALVARIABLE this Lcom/mozi/Main; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1

// access flags 0x9
public static main([Ljava/lang/String;)V
L0
LINENUMBER 17 L0
ICONST_2
ANEWARRAY java/lang/String
DUP
ICONST_0
LDC "add"
AASTORE
DUP
ICONST_1
LDC "add"
AASTORE
INVOKESTATIC java/util/Arrays.asList ([Ljava/lang/Object;)Ljava/util/List;
ASTORE 1
L1
LINENUMBER 18 L1
ALOAD 1
INVOKEINTERFACE java/util/List.iterator ()Ljava/util/Iterator; (itf)
ASTORE 2
L2
FRAME APPEND [java/util/List java/util/Iterator]
ALOAD 2
INVOKEINTERFACE java/util/Iterator.hasNext ()Z (itf)
IFEQ L3
ALOAD 2
INVOKEINTERFACE java/util/Iterator.next ()Ljava/lang/Object; (itf)
CHECKCAST java/lang/String
ASTORE 3
L4
LINENUMBER 19 L4
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ALOAD 3
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L5
LINENUMBER 20 L5
GOTO L2
L3
LINENUMBER 21 L3
FRAME CHOP 1
RETURN
L6
LOCALVARIABLE s Ljava/lang/String; L4 L5 3
LOCALVARIABLE args [Ljava/lang/String; L0 L6 0
LOCALVARIABLE list Ljava/util/List; L1 L6 1
// signature Ljava/util/List<Ljava/lang/String;>;
// declaration: list extends java.util.List<java.lang.String>
MAXSTACK = 4
MAXLOCALS = 4
}

字节码分析概览

类信息

  • 类名com.mozi.Main
  • 版本:Java 17(主版本号 61 对应 JDK 17)
  • 方法:构造函数 <init>main 方法。

1. 构造函数 <init>

字节码

1
2
3
4
5
6
7
8
9
10
public <init>()V
L0
LINENUMBER 14 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L1
LOCALVARIABLE this Lcom/mozi/Main; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
  • 作用:调用父类 Object 的构造函数。

  • 对应 Java 代码

    1
    2
    3
    4
    5
    public class Main {
    public Main() {
    super(); // 调用 Object 的构造函数
    }
    }

2. main 方法

字节码关键步骤

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
public static main([Ljava/lang/String;)V
L0
LINENUMBER 17 L0
ICONST_2 // 将常量 2 压栈(数组长度)
ANEWARRAY java/lang/String // 创建 String[2] 数组
DUP // 复制数组引用(用于后续填充)
ICONST_0 // 索引 0
LDC "add" // 加载字符串 "add"
AASTORE // 将 "add" 存入数组索引 0
DUP // 再次复制数组引用
ICONST_1 // 索引 1
LDC "add" // 加载字符串 "add"
AASTORE // 将 "add" 存入数组索引 1
INVOKESTATIC java/util/Arrays.asList (...) // 转为 List
ASTORE 1 // 存入局部变量 list (索引 1)

L1
LINENUMBER 18 L1
ALOAD 1 // 加载 list
INVOKEINTERFACE java/util/List.iterator () // 获取迭代器
ASTORE 2 // 存入局部变量 iterator (索引 2)

L2
FRAME APPEND [java/util/List java/util/Iterator]
ALOAD 2 // 加载迭代器
INVOKEINTERFACE java/util/Iterator.hasNext () // 检查是否有元素
IFEQ L3 // 无元素则跳转到 L3(结束循环)
ALOAD 2
INVOKEINTERFACE java/util/Iterator.next () // 获取元素
CHECKCAST java/lang/String // 强制转换为 String
ASTORE 3 // 存入局部变量 s (索引 3)

L4
LINENUMBER 19 L4
GETSTATIC java/lang/System.out // 加载 System.out
ALOAD 3 // 加载 s
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;) // 打印
L5
GOTO L2 // 跳回 L2(继续循环)

L3
LINENUMBER 21 L3
RETURN // 方法返回

对应 Java 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.util.Arrays;
import java.util.List;

public class Main {
public static void main(String[] args) {
// 对应 L0: 创建并填充数组,转为 List
List<String> list = Arrays.asList("add", "add");

// 遍历 List 并打印每个元素
for (String s : list) {
System.out.println(s);
}
}
}

关键字节码指令解析

  1. ANEWARRAY
    • 创建引用类型数组(这里是 String[]),长度为栈顶值 2
    • 操作后栈状态:[array_ref]
  2. DUP
    • 复制栈顶的数组引用,用于多次填充数组元素。
    • 操作后栈状态:[array_ref, array_ref]
  3. AASTORE
    • 将值存储到数组中,需要三个操作数:array_ref, index, value
    • 例如:DUP → ICONST_0 → LDC "add" → AASTORE 对应 array[0] = "add"
  4. CHECKCAST
    • 确保从迭代器获取的对象是 String 类型,否则抛出 ClassCastException
  5. FRAME
    • 指示 JVM 栈帧的变化(如局部变量和操作数栈的更新),调试信息的一部分。

局部变量表

索引 变量名 类型 作用域
0 args String[] 整个方法
1 list List<String> L1 到 L6
2 iterator Iterator L2 到 L3
3 s String 循环体内 (L4-L5)