从 i++ 和 ++i 说起局部变量表和操作数栈

JAVA herman 79浏览

最近公司有人看了尚硅谷柴林燕老师的第一季面试题,就想来考考我。我觉得柴老师讲的很好,部分内容可以延伸一下,所以写这篇文章分享给大家!

这篇文章涉及到了一点 JVM 方面的知识。面试时可能也会遇到,所以认真看不会吃亏!

int i = 1;
i = i++;
int j = i++;
int k = i + ++i * i++;
// 业余草:www.xttblog.com
System.out.println("i = " + i);
System.out.println("j = " + j);
System.out.println("k = " + k);

运行结果是:

i = 4
j = 1
k = 11

为什么是这个结果呢?我们先来看看 Java 虚拟机栈的知识点。

Java 内存可以粗糙的区分为堆内存(Heap)和栈而不是队列呢?很容易理解,因为方法内存(Stack),其中栈就是现在说的虚拟机栈,或者说是虚拟机栈中局部变量表部分。

至于为什么是栈而不是队列呢?很容易理解,因为方法调用链是最后一个调用先返回的。用队列显然不合适。

Java 虚拟机栈也是线程私有的,生命周期与线程相同。任何一个方法的执行也都是一个线程来调用的,所以前面这句话就很好理解了。

每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用、动态链接、程序出口等信息。每一个方法从调用到执行完成的过程,对应一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。

从 i++ 和 ++i 说起局部变量表和操作数栈

局部变量表存放了编译器可知的各种基本数据类型(boolean,byte,char,short,int,float,long,double)、对象引用(reference类型,不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)和returnAddressleixing (字节码指令地址)。局部变量表所需内存在编译期间完成分配,运行期间不会改变。

和局部变量区一样,操作数栈也是被组织成一个以字长为单位的数组。但是和前者不同的是,它不是通过索引来访问,而是通过标准的栈操作—压栈和出栈—来访问的。比如,如果某个指令把一个值压入到操作数栈中,稍后另一个指令就可以弹出这个值来使用。

虚拟机在操作数栈中存储数据的方式和在局部变量区中是一样的:如int、long、float、double、reference和returnType的存储。对于byte、short以及char类型的值在压入到操作数栈之前,也会被转换为int。

begin
iload_0    // push the int in local variable 0 onto the stack
iload_1    // push the int in local variable 1 onto the stack
iadd       // pop two ints, add them, push result
istore_2   // pop int, store into local variable 2
end

在这个字节码序列里,前两个指令 iload_0 和 iload_1 将存储在局部变量中索引为 0 和 1 的整数压入操作数栈中,其后 iadd 指令从操作数栈中弹出那两个整数相加,再将结果压入操作数栈。第四条指令 istore_2 则从操作数栈中弹出结果,并把它存储到局部变量区索引为 2 的位置。

0 iconst_1
 1 istore_1
 2 iload_1
 3 iinc 1 by 1
 6 istore_1
 7 iload_1
 8 iinc 1 by 1
11 istore_2
12 iload_1
13 iinc 1 by 1
16 iload_1
17 iload_1
18 iinc 1 by 1
21 imul
22 iadd
23 istore_3

所以,我们现在解释一下上面的代码。int i = 1;发生了两个过程,iconst_1 是将 int 型的 1 推送至栈顶。istore_1 把栈顶的元素弹出,并赋值给局部变量表中位置为“1”的变量,此时指变量i。这两句就相当于 int i = 1;

i = i++; 代码解释:iload_1 把局部变量表中位置为“1”的变量加载到栈顶,即把 i 的值加载到栈顶。iinc 1 by 1,将局部变量表中位置为“1”的 i 加 1,此时局部变量表中 i 的结果为 2。然后 istore_1 把栈顶的元素弹出,并赋值给局部变量表中位置为“1”的变量。所以 i 的值又被改为了 1。

int j = i++; 代码解释:iload_1 把局部变量表中位置为“1”的变量加载到栈顶,即把 i 的值加载到栈顶。iinc 1 by 1 将局部变量表中位置为“1”的 i 加 1,此时结果为 2,也就是局部变量表中 i 的结果为 2。istore_2 把栈顶的元素弹出并赋值给局部变量表中位置为“2”的 j。所以 j 是 1,但是 i 的值已经为 2。

int k = i + ++i * i++; 这个是最复杂的,我们直接看 JVM 指令即可。iload_1 把局部变量表中位置为“1”的变量加载到栈顶,即把 i 的值加载到栈顶,注意 i 的值此时是 2。iinc 1 by 1,i 自增,然后 i 就变成 3 了。接着两个 iload_1、iload_1分别把局部变量 i 压到栈了。所以栈中现在是 3、3、2。然后执行 iinc 1 by 1,i 又自增了,这时把局部变量表中的 i 就变成 4 了,注意这个 4 并未压入栈。之后 imul 进行乘法计算,栈中的前两个元素计算后是 9,之后执行 iadd 指令,也就是 9 + 2,结果为 11。最后 istore_3 把 11 从栈顶弹出,并赋值给 k,也就是局部变量表中位置为“3”的 k 的值是 11。

后面的 JVM 指令,我们就不用看了,都是打印变量到控制台中。

综上,Java 中的局部变量表和操作数栈非常的重要。下面我们通过一张图片来看看局部变量表和操作数栈之间的操作关系。

从 i++ 和 ++i 说起局部变量表和操作数栈

如果还有疑问,《尚硅谷Java视频教程_Java面试题第一季免费下载》的视频,建议你们看看!

业余草公众号

最后,欢迎关注我的个人微信公众号:业余草(yyucao)!可加QQ1群:135430763(2000人群已满),QQ2群:454796847,QQ3群:187424846。QQ群进群密码:xttblog,想加微信群的朋友,可以微信搜索:xmtxtt,备注:“xttblog”,添加助理微信拉你进群。备注错误不会同意好友申请。再次感谢您的关注!后续有精彩内容会第一时间发给您!原创文章投稿请发送至532009913@qq.com邮箱。商务合作可添加助理微信进行沟通!

本文原文出处:业余草: » 从 i++ 和 ++i 说起局部变量表和操作数栈