对象/数组/方法调用/返回指令
面向对象的字节码世界
这一节是字节码指令中最「重磅」的部分——对象的创建、字段访问、数组操作、方法调用和返回。理解了这些,你就能看透 Java 源码编译后到底发生了什么。
对象的创建与访问
创建对象:new
new #class_index // 创建对象实例new 指令在堆上分配内存并初始化字段(零值),但不调用构造函数:
java
// 源码
User user = new User();
// 字节码
new #7 // 创建 User 对象(分配内存,字段置零)
dup // 复制引用(稍后传给构造函数)
invokespecial #8 // 调用构造函数 <init>
astore_1 // 存入局部变量为什么要 dup?因为 invokespecial 会弹出对象引用,构造函数执行完后栈就空了。没有 dup 的话,astore_1 就找不到引用了。
访问字段:getfield / putfield
getfield #field_index // 获取实例字段值
putfield #field_index // 设置实例字段值java
// 源码
String name = user.name; // 读取字段
user.name = "Alice"; // 写入字段getfield #2 // 获取 #2 字段(name)
astore_2 // 存入 name 变量
aload_1 // user 引用入栈
ldc #3 // "Alice" 入栈
putfield #2 // 设置 #2 字段(name)访问静态字段:getstatic / putstatic
getstatic #field_index // 获取静态字段
putstatic #field_index // 设置静态字段java
// 源码
int count = Counter.total; // 读静态字段
Counter.total = 100; // 写静态字段getstatic #5 // 获取 #5 静态字段(total)
istore_1 // 存入 count
sipush 100
putstatic #5 // 设置 total = 100检查类型:instanceof / checkcast
instanceof #class_index // 检查是否为某类型,返回 0 或 1
checkcast #class_index // 检查类型,失败则 ClassCastExceptionjava
// 源码
if (obj instanceof String) {
String s = (String) obj;
}aload_1 // obj 入栈
instanceof #10 // 检查是否是 String
ifeq 12 // 不是,跳到偏移 12
aload_1 // obj 入栈
checkcast #10 // 强制转换 String
astore_2 // 存入 s两者的区别:instanceof 不抛异常,结果是 boolean;checkcast 失败抛 ClassCastException。
数组的创建与操作
创建数组
newarray #atype // 创建基本类型数组
anewarray #class_index // 创建引用类型数组
multianewarray #dims // 创建多维数组newarray 的 atype 参数:
4 = T_BOOLEAN
5 = T_CHAR
6 = T_FLOAT
7 = T_DOUBLE
8 = T_BYTE
9 = T_SHORT
10 = T_INT
11 = T_LONGjava
// 源码
int[] arr = new int[10];
String[] names = new String[5];bipush 10 // 数组长度 10 入栈
newarray 10 // 创建 int[](atype = 10)
astore_1 // 存入 arr
bipush 5 // 数组长度 5 入栈
anewarray #3 // 创建 String[](class_index → String)
astore_2 // 存入 names数组加载:iaload 系列
iaload // int[] → int 入栈
laload // long[] → long 入栈
faload // float[] → float 入栈
daload // double[] → double 入栈
aaload // Object[] → reference 入栈
baload // byte[]/boolean[] → int 入栈
caload // char[] → int 入栈
saload // short[] → int 入栈执行规则:栈上需要 [arrayref, index],弹出后加载 array[index] 入栈:
aload_1 // 数组引用 arr 入栈
iconst_0 // 索引 0 入栈
iaload // 加载 arr[0],出栈 [arr, 0],入栈 [值]数组存储:iastore 系列
iastore // int 入栈 → 存入 int[]
lastore // long 入栈 → 存入 long[]
fastore // float 入栈 → 存入 float[]
dastore // double 入栈 → 存入 double[]
aastore // reference 入栈 → 存入 Object[]
bastore // int 入栈 → 存入 byte[]/boolean[]
castore // int 入栈 → 存入 char[]
sastore // int 入栈 → 存入 short[]执行规则:栈上需要 [arrayref, index, value],弹出后存入:
aload_1 // 数组引用 arr 入栈
iconst_0 // 索引 0 入栈
bipush 42 // 值 42 入栈
iastore // 存入 arr[0] = 42数组长度
arraylength // 获取数组长度java
// 源码
int len = arr.length;
// 字节码
aload_1 // arr 入栈
arraylength // 获取长度,入栈
istore_2 // 存入 len多维数组:multianewarray
java
// 源码
int[][] matrix = new int[3][4];bipush 3
bipush 4
multianewarray #dims_index, 2 // 创建 2 维数组
astore_1方法调用指令
这是字节码中最复杂的部分。JVM 有 5 种方法调用指令,每种有不同的用途和语义。
5 种方法调用指令对比
| 指令 | 用途 | 静态/动态分派 | 说明 |
|---|---|---|---|
invokestatic | 调用静态方法 | 静态分派 | 用于 static 方法 |
invokespecial | 调用构造器/私有/父类方法 | 静态分派 | <init>、私有方法、super.xxx() |
invokevirtual | 调用普通实例方法 | 动态分派 | 普通方法,有虚分派 |
invokeinterface | 调用接口方法 | 动态分派 | 接口方法实现 |
invokedynamic | 调用动态方法 | 完全动态 | Lambda、方法句柄 |
invokespecial:构造器、私有方法、super
invokespecial #method_index // 调用构造器、私有方法或 super 方法java
// 源码:调用构造函数
new User()
dup
invokespecial #8 // 调用 User.<init>java
// 源码:调用私有方法
private void init() { }
invokespecial #15 // 调用 this.init()java
// 源码:调用父类方法
super.toString()
invokespecial #20 // 调用 java/lang/Object.toStringinvokestatic:静态方法
invokestatic #method_index // 调用静态方法java
// 源码
int max = Math.max(10, 20);
// 字节码
bipush 10
bipush 20
invokestatic #6 // 调用 Math.max(int, int)
istore_1invokevirtual:虚方法调用
invokevirtual #method_index // 调用实例方法(动态分派)java
// 源码
String s = user.getName();aload_1 // user 引用入栈
invokevirtual #12 // 调用 getName()
astore_2 // 存入 s动态分派的原理:JVM 在运行时根据对象的实际类型决定调用哪个方法。父类和子类可能有不同版本的 getName(),运行时才能确定。
invokeinterface:接口方法
invokeinterface #method_index, count // 调用接口方法java
// 源码
List<String> list = new ArrayList<>();
list.add("hello");new #13 // new ArrayList
dup
invokespecial #14 // ArrayList()<init>
astore_1
aload_1 // list 入栈
ldc #15 // "hello" 入栈
invokeinterface #16, 2 // list.add(String),count=2(含 this)为什么需要 count?count 是方法参数个数(包含 this),用于 JVM 验证栈帧。
invokedynamic:动态方法调用
这是 JDK 7 引入的指令,用于支持 Lambda 和方法句柄:
java
// 源码:Lambda 表达式
Runnable r = () -> System.out.println("hello");invokedynamic #25, 0 // 调用 Lambda 引导方法
astore_1 // 存入 rinvokedynamic 的执行流程:
invokedynamic #bootstrap_method, #dynamic_method_name
│
├── 调用引导方法(Bootstrap Method)
│ │
│ └── 返回 CallSite(包含 MethodHandle)
│
└── 通过 CallSite 找到实际方法并调用引导方法是由 java.lang.Invoke 包中的 LambdaMetafactory 提供的,它负责生成 Lambda 对应的类或方法。
返回指令
ireturn // 返回 int/float/boolean/byte/char/short
lreturn // 返回 long
freturn // 返回 float
dreturn // 返回 double
areturn // 返回 reference
return // 返回 voidjava
// 源码
public int getValue() { return 42; }
// 字节码
bipush 42
ireturnjava
// 源码:void 方法
public void print() { System.out.println("hi"); }
// 字节码
...
return完整示例:方法调用全流程
java
public class Demo {
private int value;
public Demo(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static void main(String[] args) {
Demo d = new Demo(10);
int v = d.getValue();
System.out.println(v);
}
}字节码:
bash
javap -c Demo.class
# public Demo(int);
# Code:
# 0: aload_0 // this 入栈
# 1: invokespecial #1 // Object.<init>()
# 4: aload_0 // this 入栈
# 5: iload_1 // value 参数入栈
# 6: putfield #2 // this.value = value
# 9: return
# public int getValue();
# Code:
# 0: aload_0 // this 入栈
# 1: getfield #2 // 获取 this.value
# 4: ireturn // 返回
# public static void main(java.lang.String[]);
# Code:
# 0: new #3 // 创建 Demo 对象
# 3: dup // 复制引用
# 4: bipush 10 // 参数 10 入栈
# 6: invokespecial #4 // Demo(int) 构造器
# 9: astore_1 // 存入局部变量 d
# 10: aload_1 // d 入栈
# 11: invokevirtual #5 // 调用 getValue()
# 14: istore_2 // 存入 v
# 15: getstatic #6 // System.out
# 18: iload_2 // v 入栈
# 19: invokevirtual #7 // println(int)
# 22: return本节小结
对象/数组/方法调用核心要点:
| 类别 | 指令 | 说明 |
|---|---|---|
| 创建对象 | new | 分配内存,字段置零,不调用构造器 |
| 读写字段 | getfield/putfield | 实例字段 |
| 读写静态字段 | getstatic/putstatic | 静态字段 |
| 创建数组 | newarray/anewarray | 基本类型/引用类型数组 |
| 数组操作 | iaload/iastore 等 | 加载和存储 |
| 调用静态方法 | invokestatic | 静态分派 |
| 调用构造器/私有/super | invokespecial | 静态分派 |
| 调用实例方法 | invokevirtual | 动态分派(多态) |
| 调用接口方法 | invokeinterface | 动态分派 |
| 调用 Lambda | invokedynamic | 完全动态 |
| 返回 | ireturn/lreturn 等 | 按类型返回 |
5 种方法调用指令中,invokevirtual 是最常用的,它实现了 Java 的运行时多态。
下一节,我们来看 操作数栈/比较/跳转指令。
