Java字节码
Java 代码编译后生成 .class 文件,其中包含字节码指令。JVM 基于栈结构执行字节码,每条指令由 1 字节操作码(0-255)和零或多个操作数组成。
// Java 源码
public int add(int a, int b) {
int result = a + b;
return result;
}
// 字节码指令
public int add(int, int);
0: iload_1 // 将局部变量 a 压入栈
1: iload_2 // 将局部变量 b 压入栈
2: iadd // 执行加法
3: istore_3 // 将结果存回 result
4: ireturn // 返回结果字节码知识结构:
Class 文件结构
ClassFile {
u4 magic; // 魔数:0xCAFEBABE
u2 minor_version; // 次版本号
u2 major_version; // 主版本号(55=JDK11, 52=JDK8)
u2 constant_pool_count; // 常量池数量
cp_info constant_pool[]; // 常量池
u2 access_flags; // 访问标志
u2 this_class; // 当前类索引
u2 super_class; // 父类索引
u2 interfaces_count; // 接口数量
u2 interfaces[]; // 接口集合
u2 fields_count; // 字段数量
field_info fields[]; // 字段集合
u2 methods_count; // 方法数量
method_info methods[]; // 方法集合
u2 attributes_count; // 属性数量
attribute_info attributes[]; // 属性集合
}访问标志速查
ACC_PUBLIC(0x0001)、ACC_PRIVATE(0x0002)、ACC_PROTECTED(0x0004)、ACC_STATIC(0x0008)、ACC_FINAL(0x0010)、ACC_SUPER(0x0020)、ACC_INTERFACE(0x0200)、ACC_ABSTRACT(0x0400)
javap 反编译工具
javap 是 JDK 内置的反编译命令,可将 .class 文件转换为可读形式。
| 参数 | 说明 |
|---|---|
-v | 详细模式(含常量池、属性) |
-p | 显示所有成员(含 private) |
-c | 显示字节码指令 |
-l | 显示行号和局部变量表 |
-s | 显示内部类型签名 |
-verbose | 输出完整类文件信息 |
javap Main.class # 基础反编译
javap -v -p Main.class # 详细字节码
javap -c Main.class # 单方法字节码Code 属性详解
Code:
stack=2, locals=1, args_size=1 // 栈深度、局部变量数、参数个数
0: aload_0
1: invokespecial #1
4: return
LineNumberTable:
line 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lcom/example/Main;| 属性 | 说明 |
|---|---|
stack | 最大操作数栈深度 |
locals | 局部变量表容量(单位:槽) |
args_size | 方法参数个数(实例方法含 this) |
LineNumberTable | 源码行号与字节码偏移映射 |
LocalVariableTable | 局部变量与源码变量的映射 |
注意
实例方法的局部变量表第 0 个槽位总是存储 this 引用,即使没有显式参数。
字节码指令分类
加载与存储指令
在局部变量表和操作数栈之间传输数据。
| 指令 | 说明 |
|---|---|
xload_<n> | 将第 n 个局部变量压栈(n=0-3) |
xload | 通过参数指定槽位压栈 |
xstore_<n> | 栈顶值存入局部变量(n=0-3) |
xstore | 通过参数指定槽位存储 |
类型前缀:i=int、l=long、f=float、d=double、a=reference
public void load(int age, String name, long birthday, boolean sex) {
iload_1 // int 参数 age
aload_2 // String 引用 name
lload_3 // long 参数 birthday
iload 5 // boolean 参数 sex
}常量加载指令
| 指令 | 范围 | 说明 |
|---|---|---|
iconst_<n> | -1 ~ 5 | int 小常量 |
bpush | -128 ~ 127 | byte 常量 |
sipush | -32768 ~ 32767 | short 常量 |
ldc | int/float/String | 从常量池加载 |
ldc2_w | long/double | 从常量池加载(宽) |
指令选择
JVM 根据常量值范围自动选择最高效的指令,-1~5 用 iconst,-128~127 用 bpush,更大值走 ldc。
算术指令
| 分类 | 指令 |
|---|---|
| 加法 | iadd、ladd、fadd、dadd |
| 减法 | isub、lsub、fsub、dsub |
| 乘法 | imul、lmul、fmul、dmul |
| 除法 | idiv、ldiv、fdiv、ddiv |
| 取余 | irem、lrem、frem、drem |
| 取反 | ineg、lneg、fneg、dneg |
注意
整数除以零抛出 ArithmeticException;浮点数除以零得到 Infinity,不抛异常。
类型转换指令
宽化(自动):i→l→f→d,无精度损失。窄化(强制):必须显式转换,可能丢失精度。
int i = 100;
long l = i; // 宽化:i2l
int j = (int) l; // 窄化:l2i对象创建与访问指令
| 指令 | 说明 |
|---|---|
new | 创建类实例 |
newarray | 创建基本类型数组 |
anewarray | 创建引用类型数组 |
multianewarray | 创建多维数组 |
getfield / putfield | 实例字段读写 |
getstatic / putstatic | 静态字段读写 |
arraylength | 获取数组长度 |
数组元素访问用 xaload / xastore(x 为类型前缀),引用数组统一为 aaload / aastore。
方法调用指令
方法调用是字节码最复杂的部分,不同指令对应不同的分派策略:
| 指令 | 场景 |
|---|---|
invokevirtual | 普通实例方法(虚方法表分派) |
invokeinterface | 接口方法 |
invokespecial | 构造方法、私有方法、super 调用 |
invokestatic | 静态方法 |
invokedynamic | Lambda / 动态代理 |
class Father {
public void eat() { } // invokevirtual
private void sleep() { } // invokespecial
public static void run() { } // invokestatic
}
class Son extends Father {
@Override
public void eat() {
super.eat(); // invokespecial(super 调用)
Father.run(); // invokestatic
}
}Lambda 的秘密
Lambda 表达式通过 invokedynamic 实现——编译时生成引导方法,运行时动态生成 Lambda 类,避免了匿名内部类的额外 .class 文件开销。
操作数栈管理指令
| 指令 | 说明 |
|---|---|
pop / pop2 | 弹出 1/2 个 slot |
dup | 复制栈顶值 |
dup_x1 | 复制栈顶并插入更深位置 |
swap | 交换栈顶两个值 |
控制转移指令
条件跳转:
| 指令 | 条件 |
|---|---|
ifeq / ifne | 等于/不等于 0 |
iflt / ifge | 小于/大于等于 0 |
ifgt / ifle | 大于/小于等于 0 |
ifnull / ifnonnull | 引用为空/不为空 |
if_icmpeq / if_icmpne | int 相等/不等 |
if_icmplt / if_icmpge | int 小于/大于等于 |
switch 指令:tableswitch(case 连续,O(1))和 lookupswitch(case 离散,需搜索)。
无条件跳转:goto(2 字节偏移)、goto_w(4 字节偏移)。
异常处理
Exception table:
from to target type
0 4 7 Class java/lang/ArithmeticExceptionJVM 通过异常表(而非跳转指令)实现异常处理。from-to 是监控的字节码范围,target 是处理入口,type 是异常类型。
synchronized 字节码
0: aload_0
1: dup
2: astore_1
3: monitorenter // 获取锁
4: aload_1
5: monitorexit // 正常释放锁
6: goto 14
9: astore_2
11: aload_1
12: monitorexit // 异常释放锁(确保不锁死)
13: aload_2
15: athrow注意
monitorexit 在正常退出和异常退出路径上都会执行——异常表保证 synchronized 块不会锁死。
字段描述符
| 描述符 | 类型 | 描述符 | 类型 |
|---|---|---|---|
B | byte | J | long |
C | char | S | short |
D | double | Z | boolean |
F | float | V | void |
I | int | L | 引用类型 |
[ | 数组 |
I→ int、Ljava/lang/String;→ String、[I→ int[](II)V→ 参数 int, int,返回 void
常量池
常量池存放字面量(字符串、final 常量)和符号引用(类全限定名、字段/方法名称和描述符)。
| 类型 | 标志 | 说明 |
|---|---|---|
CONSTANT_Utf8 | 1 | UTF-8 编码字符串 |
CONSTANT_Integer | 3 | 整型常量 |
CONSTANT_Float | 4 | 浮点常量 |
CONSTANT_Long | 5 | 长整型常量 |
CONSTANT_Double | 6 | 双精度常量 |
CONSTANT_Class | 7 | 类或接口引用 |
CONSTANT_String | 8 | 字符串引用 |
CONSTANT_Fieldref | 9 | 字段引用 |
CONSTANT_Methodref | 10 | 方法引用 |
CONSTANT_InterfaceMethodref | 11 | 接口方法引用 |
CONSTANT_NameAndType | 12 | 字段或方法的部分引用 |
工具推荐
| 工具 | 说明 |
|---|---|
javap | JDK 内置反编译工具 |
jclasslib | IntelliJ IDEA 插件,可视化字节码 |
ASM | 字节码操作库 |
BCEL | 字节码工程库 |
Arthas | 阿里诊断工具,可在线查看字节码 |