Skip to content

对象/数组/方法调用/返回指令

面向对象的字节码世界

这一节是字节码指令中最「重磅」的部分——对象的创建、字段访问、数组操作、方法调用和返回。理解了这些,你就能看透 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     // 检查类型,失败则 ClassCastException
java
// 源码
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_LONG
java
// 源码
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.toString

invokestatic:静态方法

invokestatic #method_index    // 调用静态方法
java
// 源码
int max = Math.max(10, 20);

// 字节码
bipush        10
bipush        20
invokestatic #6    // 调用 Math.max(int, int)
istore_1

invokevirtual:虚方法调用

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&lt;String&gt; list = new ArrayList&lt;&gt;();
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)

为什么需要 countcount 是方法参数个数(包含 this),用于 JVM 验证栈帧。

invokedynamic:动态方法调用

这是 JDK 7 引入的指令,用于支持 Lambda 和方法句柄:

java
// 源码:Lambda 表达式
Runnable r = () -> System.out.println("hello");
invokedynamic #25, 0    // 调用 Lambda 引导方法
astore_1                // 存入 r

invokedynamic 的执行流程:

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     // 返回 void
java
// 源码
public int getValue() { return 42; }
// 字节码
bipush        42
ireturn
java
// 源码: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静态分派
调用构造器/私有/superinvokespecial静态分派
调用实例方法invokevirtual动态分派(多态)
调用接口方法invokeinterface动态分派
调用 Lambdainvokedynamic完全动态
返回ireturn/lreturn按类型返回

5 种方法调用指令中,invokevirtual 是最常用的,它实现了 Java 的运行时多态。

下一节,我们来看 操作数栈/比较/跳转指令

基于 VitePress 构建