从虚拟机栈&字节码角度来聊聊值传递

面试中聊到了值传递,临场尝试用虚拟机栈的角度去解释值传递的过程,讲的稍微有些混乱,在这里对这次讨论做一下整理。

值传递 & 引用传递

首先先明确,程序设计语言中有两类将值传递给方法的方式:

  • 值传递:方法的形参是实参值的拷贝
  • 引用传递:方法接收的直接是实参所引用的对象在堆中的地址,不会创建副本,对形参的修改将影响到实参。

在 Java 中,只提供了值传递的方式。

从 JVM 虚拟机栈的角度看值传递

从 JVM 虚拟机栈的角度来看,方法调用时的参数传递本质上是通过「调用方法栈帧的操作数栈」向「被调用方法栈帧的局部变量表」复制数据的过程,且所有参数传递均是「值传递」。以下通过一个具体案例和 JVM 字节码解析完整过程:


示例代码与 JVM 内存结构

public class ParameterDemo {
    public static void main(String[] args) {
        int a = 10;
        int[] arr = {1, 2, 3};
        modify(a, arr);
    }

    public static void modify(int num, int[] array) {
        num = 20;
        array[0] = 100;
    }
}

JVM 内存结构关键组件

  1. 虚拟机栈(Stack):每个线程独有,存储栈帧(Frame)。
  2. 栈帧(Frame):包含局部变量表(Local Variables)、操作数栈(Operand Stack)、动态链接等。
  3. 堆(Heap):存储对象实例和数组。

参数传递的详细过程

步骤 1:main 方法栈帧初始化

  • 局部变量表
  • 操作数栈:初始为空。

步骤 2:调用 modify(a, arr) 前的准备

  1. 加载参数到操作数栈
  • 通过 iload_1 将局部变量表索引 1(a=10)压入操作数栈。
  • 通过 aload_2 将局部变量表索引 2(arr 的引用地址)压入操作数栈。 操作数栈状态
   | 10    |
   | arr@  |
  1. 触发方法调用指令
   invokestatic ParameterDemo.modify(int, int[]) // 调用静态方法

步骤 3:创建 modify 方法栈帧

  1. 新建栈帧
  • 局部变量表大小 = 参数数量 + 可能的额外空间(如 this,但此处是静态方法无 this)。
  • 参数复制将操作数栈中的值按顺序复制到新栈帧的局部变量表。 modify 栈帧的局部变量表

步骤 4:执行 modify 方法体

  1. 修改 num
   num = 20; // 对应字节码:bipush 20 -> istore_0
  • 将 20 压入操作数栈,再存储到局部变量表索引 0。
  • 仅修改副本,原 main 中的 a 仍为 10。 局部变量表更新: 索引 变量名 值 0 num 20
  1. 修改 array[0]
   array[0] = 100; // 对应字节码:aload_1 -> iconst_0 -> bipush 100 -> iastore
  • 通过局部变量表索引 1(array)获取引用地址,找到堆中的数组对象。
  • 修改堆中数组第 0 个元素为 100。
  • 堆数据被修改main 中的 arr 指向同一对象,因此变化可见。 堆内存变化
   原数组:[1, 2, 3]
   修改后:[100, 2, 3]

步骤 5:方法返回与栈帧销毁

  1. modify 方法执行完毕
  • 弹出 modify 栈帧,回到 main 方法栈帧。
  • 操作数栈恢复为空,继续执行后续指令(此处无更多操作)。
  1. 最终内存状态
  • main 中的 a 保持 10(基本类型值未变)。
  • main 中的 arr[0] 变为 100(堆数据被修改)。

关键结论

  1. 值传递的本质
  • 基本类型:传递值的副本,修改副本不影响原值。
  • 引用类型:传递引用地址的副本,副本与原引用指向同一堆对象,因此通过副本修改对象内容会影响原数据,但修改引用本身(如 array = null)不影响原引用。
  1. JVM 操作规范
  • 参数通过操作数栈传递,复制到被调用方法的局部变量表。
  • 方法调用通过 invokestaticinvokevirtual 等指令实现,参数按顺序压栈。
  • 堆数据由所有线程共享,栈帧销毁不影响堆中已修改的数据。

字节码验证

通过 javap -c ParameterDemo.class 查看字节码:

public static void main(java.lang.String[]);
  Code:
     0: bipush        10      // 将10压入操作数栈
     2: istore_1              // 存储到局部变量表索引1(a=10)
     3: iconst_3              // 准备创建数组
     4: newarray       int
     6: dup
     7: astore_2              // 存储数组引用到索引2(arr)
     ...
    16: iload_1               // 加载a的值到操作数栈
    17: aload_2               // 加载arr的引用到操作数栈
    18: invokestatic  #2      // 调用modify方法
    21: return

public static void modify(int, int[]);
  Code:
     0: bipush        20      // 将20压入操作数栈
     2: istore_0              // 存储到局部变量表索引0(num=20)
     3: aload_1               // 加载array引用
     4: iconst_0
     5: bipush        100
     7: iastore               // 修改堆中数组元素
     8: return

结论:字节码明确展示了参数传递和修改过程,与上述分析完全一致。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇