【java】从内存层面理解,为什么 int i = 0; i = i++; i的结果为0

发布时间:2023-05-10 17:17:04

  一、前言

  如果你理解JVM的内存模型,就不难理解为什么答案是0,而不是1。 我们单独看问题中的这两个代码。 int i = 0; i = i++;return i; 二、从内存开始

  Java虚拟机栈(JVM Stack)描述的是Java方法执行的内存模型,而JVM内存模型是基于“栈帧”,每个栈帧都有 局部变量表 和 操作数栈 (还有动态链接,return address等。),那么JVM是如何执行这个句子的呢?上述两行代码可以通过javap大致翻译成以下JVM指令执行代码。 0: iconst_0 1: istore_1 2: iload_1 3: iinc 1, 1 6: istore_1 7: iload_1

  我们可以直接使用javap -c xxx.class 直接查看jvm执行的代码:

【java】从内存层面理解,为什么 int i = 0; i = i++; i的结果为0_JVM

  接下来分析一下JVM是如何执行的: 第0:将int类型的0放入栈中,就是放在操作数栈的栈顶上 第1:弹出操作数栈顶值0,并将其保存到局部变量表中 index (索引)值为1的位置。(局部变量表也从0开始,当前实例的this引用一般保存在0位,当然除了静态方法,因为静态方法是类而不是实例方法) 第2:局部变量表index 1位置值的副本进入栈中。(此时局部变量表index为1的值为0,操作数栈顶的值为0) 第3:inc是int类型值的自增操作。后面的第一个值1表示局部变量表的index值,表示Inc操作需要执行此值,第二个值1表示需要增加的值。(此时,由于自增操作的执行,局部变量表index为1的值变为1,但操作数栈中栈顶的值仍为0) 第6:将操作数栈顶的值弹出(值0),放在局部变量表index为1的位置(旧值:1,新值:0),覆盖上一步局部变量表的计算结果。 第7:局部变量表index 1位置值的副本进入栈中。(此时局部变量表index为1的值为0,操作数栈顶的值为0) 总结

  从执行顺序可以看出,这里第一和第六执行了两次操作,将0赋值给变量i(=号赋值),i++操作在这两个操作之间进行。自增操作是局部变量表中的值自增,栈顶值没有变化。这里需要注意的是,保存这个初始值的地方是操作数栈而不是局部变量表,最后将栈顶值覆盖到局部变量表所在的索引位置。

  感兴趣的学生可以了解JVM的栈帧(Stack Frame)

  关于第二个陷阱(为什么 fermin方法不影响i值 )看下面的答案。 inc.fermin(i); java方法之间的参数传递是 值传递 而不是 引用传递 每种方法都有一个栈帧,是方法运行时的数据结构。也就是说,每种方法都有自己独有的局部变量表。(更严格的说法是,每个线程在执行每个方法时都有自己的栈帧,或者当前的栈帧 current stack frame) intt的形式参数被调用方法fermin() i 实际上是main()调用方法的实际参数 i 副本。 通过局部变量表实现方法之间的参数传递,main()调用fermin()方法时,传递了两个参数:第0个隐式参数是当前的实例(Inc inc = new Inc(); 是inc引用的副本,引用/reference 是指向对象的地址,32位系统占用4个字节,也就是说,用Slot保存对象reference,实际上是reference的副本,而不是 reference本身 ); 第一个显示参数是 i 副本。所以 fermin()方法对 i 执行操作仅限于其方法独有或可见的局部变量表,main()方法中局部变量表中的i不受其影响;

  如果main()方法和fermin()方法共享局部变量表,答案的结果会有所不同。 事实上,如果你自己思考,你会发现, JVM虚拟机团队这样设计是有道理的。

上一篇 【Java】疯狂Java基础(四)——String, StringBuffer,StringBuilder的区别
下一篇 【MQ】Centos7下安装RabbitMQ

文章素材均来源于网络,如有侵权,请联系管理员删除。

标签: Java教程Java基础Java编程技巧面试题Java面试题