本文转载自:从 i++ 和 ++i 说起局部变量表和操作数栈

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

这篇文章涉及到了一点 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 虚拟机栈中入栈和出栈的过程。

局部变量表存放了编译器可知的各种基本数据类型(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 说起局部变量表和操作数栈的更多相关文章

  1. JVM探秘6--图解虚拟机栈的局部变量表和操作数栈工作流程

    案例代码如下: public class JVMTest { public static Integer num = 10; public int add(int i){ int j = 5; int ...

  2. Java中的局部变量表及使用jclasslib进行查看

    直接上下载地址 jclasslib是一个独立的工具,不是包含在JDK中的工具,需要自己进行下载,下载地址如下: http://downfile.downcc.com/down/JClassLib_wi ...

  3. JVM-栈帧之局部变量表

    1.栈帧的内部结构 每个栈帧中存储着: 局部变量表(Local Variables) 操作数栈(Operand Stack)(或表达式栈) 动态链接(Dynamic Linking)(或指向运行时常量 ...

  4. JAVA 局部变量表

    1. 除了 long,double 占用两个slot 之外,其他类型均占用一个slot. 2.在内容相同的情况下, 实例方法(不加 static) 会比 类方法 (static)对占用一个局部变量位置 ...

  5. java虚拟机 jvm 局部变量表实战

    java局部变量表是栈帧重要组中部分之一.他主要保存函数的参数以及局部的变量信息.局部变量表中的变量作用域是当前调用的函数.函数调用结束后,随着函数栈帧的销毁.局部变量表也会随之销毁,释放空间. 由于 ...

  6. JVM 栈帧之操作数栈与局部变量表

    目录 前置知识 引子 基于寄存器的设计模式 基于栈的设计模式 一个简单的例子 如何查看局部变量表? 实例方法中的局部变量表 结论 前置知识 阅读本文需要对以下知识有所了解: * 栈 * 汇编 * Ja ...

  7. 局部变量表中Slot复用对垃圾回收的影响详解

    看两段代码 1. package com.jvm; public class Test { public static void main(String[] args) { { byte[] plac ...

  8. 栈帧的内部结构--局部变量表(Local Variables)

    每个栈帧中包含: 局部变量表(Local Variables) 操作数栈(Opreand Stack) 或表达式栈 动态链接 (Dynamic Linking) (或指向运行时常量的方法引用) 动态返 ...

  9. C++模板实现动态顺序表(更深层次的深浅拷贝)与基于顺序表的简单栈的实现

    前面介绍的模板有关知识大部分都是用顺序表来举例的,现在我们就专门用模板来实现顺序表,其中的很多操作都和之前没有多大区别,只是有几个比较重要的知识点需要做专门的详解. #pragma once #inc ...

随机推荐

  1. 笔记39 Spring Web Flow——订单流程(收集顾客信息)

    如果你曾经订购过披萨,你可能会知道流程.他们首先会询问你的电 话号码.电话号码除了能够让送货司机在找不到你家的时候打电话给 你,还可以作为你在这个披萨店的标识.如果你是回头客,他们可以 使用这个电话号 ...

  2. VS项目种类GUID

    在VS里新建的类库项目,在添加新建项时往往找不到模板.比如想新建一个WPF的资源词典文件,VS认为该类库项目不是WPF类型,就没有列出新建资源词典的模板.解决办法是在csproj文件的<Prop ...

  3. [转]Netty入门(最简单的Netty客户端/服务器程序)

    Java中的NIO是一种解决阻塞式IO问题的基本技术,但是NIO的编写对java程序员是有比较高的要求的.那么Netty就是一种简化操作的一个成熟的网络IO编程框架.这里简单介绍一个程序,代码是< ...

  4. Spring Boot学习笔记一

    Spring Boot简介 前言:本章简单介绍Spring boot的使用. (第二天springboot的学习之路:https://www.cnblogs.com/LBJLAKERS/p/12003 ...

  5. mui请求数据接口问题

    今天我在本地模拟做一个数据请求,第一次用的mui自带的方法来请求数据,当时我的本地接口地址是http://localhost:8087/jeecg/sightseerController.do?che ...

  6. JAVA Java中@Override的作用

    @Override是伪代码,表示重写(当然不写也可以),不过写上有如下好处: 1.可以当注释用,方便阅读: 2.编译器可以给你验证@Override下面的方法名是否是你父类中所有的,如果没有则报错.例 ...

  7. BZOJ 4517: [Sdoi2016]排列计数(组合数学)

    题面 Description 求有多少种长度为 n 的序列 A,满足以下条件: 1 ~ n 这 n 个数在序列中各出现了一次 若第 i 个数 A[i] 的值为 i,则称 i 是稳定的.序列恰好有 m ...

  8. splay 模板 洛谷3369

    题目描述 您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作: 插入 xx 数 删除 xx 数(若有多个相同的数,因只删除一个) 查询 xx 数的排名(排名定义为比当前数小的数 ...

  9. cdq分治·三维偏序问题

    转载自FlashHu大佬的博客CDQ分治总结(CDQ,树状数组,归并排序),在讲述部分有部分删改,用了自己的代码 CDQ分治的思想 CDQ分治是基于时间的离线分治算法.这一类分治有一个重要的思想——用 ...

  10. 转:这里有些sscanf()的一些使用说明,都是从论坛,Blog里整理出来的。供大家使用。

    http://www.cnblogs.com/gmh915/archive/2009/09/30/1576995.html 这里有些sscanf()的一些使用说明,都是从论坛,Blog里整理出来的.供 ...