java可以进行数字的加减乘除,但是JVM的运算步骤是什么样子呢?从一个神奇的式子入手,研究下JVM到底做了什么?

先看下图:

简直亮瞎了我的钛合金狗眼,刚看到这个式子的时候,我百思不得其解,最后在群友们和一个同事的解释下,才明白,这和JVM对算术运算的操作原理有关系,其实就是一个入栈和出栈的操作过程。

如果不明白底层的栈操作,乍一看这个式子,简直就像是突破了自己的常识,就算是运算符的优先级,那也是这样:

  1. 第一步,计算 (b = a),那么 b此时已经是100
  2. 第二步,计算 (b = a) * 0 = 100 * 0 = 0
  3. 第三步,计算 b + (b = a) * 0 = 100 + 0 = 100
  4. 第四步,把 b = 100 赋给 a ,那么 a 也是 100

正常来说,我脑补出来的过程就是这样的顺序,但是结果很打脸, b 是 100 我 还能理解,但是 a 不应该也是 100 么,漏掉了什么呢?难道还同时存在两个 b ? 第一个 b 和 第 二 b 的值其实是不一样的?怎么可能呢?在第一步的时候不是已经把 b 的值 给改掉了吗?

肯定是我漏掉了什么,上面的运行步骤肯定是漏了什么,是哪一部分呢,是入栈和出栈的真实过程,程序并不是按我主观想象来运行的,而是依赖于作者的底层设计。只能是同时存在两个b , 第一个 b 和 第二个 b 其实是不一样的,互不影响的,才能解释程序运行的结果。但是,具体是怎么样的一个过程呢?

在说之前,先补一补 这个东西。

虚拟机栈:表示Java方法执行的内存模型,每调用一个方法就会为每个方法生成一个栈帧(Stack Frame),用来存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法被调用和完成的过程,都对应一个栈帧从虚拟机栈上入栈和出栈的过程。虚拟机栈的生命周期和线程是相同的。

不过我们这里要说的栈还不是虚拟机栈,而是虚拟机栈里,一个栈帧的操作数栈。因为,我这里只是演示一个方法而已,这一个方法其实就是一个栈帧。

是 后入先出(Last In First Out)LIFO栈。

借周老师一张图:

一个栈帧主要由四部分组成:

  • 局部变量表
  • 操作栈(也叫操作数栈)
  • 动态连接
  • 返回地址信息

具体有机会再啰嗦,还是让我们回归正题,分析一下这段代码:

打开CMD: 执行java命令

我截取主要的字节码,来捋一下程序的真实运行情况:

    Code:
stack=3, locals=3, args_size=1
0: bipush 100
2: istore_1
3: sipush 200
6: istore_2
7: iload_2
8: iload_1
9: dup
10: istore_2
11: iconst_0
12: imul
13: iadd
14: istore_1
15: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
18: new #3 // class java/lang/StringBuilder
21: dup
22: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
25: ldc #5 // String a =
27: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
30: iload_1
31: invokevirtual #7 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
34: ldc #8 // String ,b =
36: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
39: iload_2
40: invokevirtual #7 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
43: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
46: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
49: return

在这里主要用到的就是 操作数栈和局部变量表 数值之间的来回倒腾。

从操作数栈 到 局部变量表 是出栈(也叫弹栈)的过程,一般用单词 store 相关的指令。

从局部变量表 到 操作数栈 是入栈(也叫压栈)的过程 ,意从局部变量表到操作数栈,数值是复制一份过去,并不改变局部变量表里的值,一般用 单词 load 相关的指令。

再次说一下栈这个东西,类比现实中的事物,就和 汉诺塔 一样,就下图这玩意:

你先放入的,只能最后才能拿出来,因为它只有一个出口。

操作数栈,这个命名其实很贴切,就是为了操作,就是为了操作数的一个后入先出的东西。

继续看字节码:

这块是我这个方法最主要的部分。

stack=3 , 表示 这个栈帧的 操作数栈的深度,最多是3,就像汉诺塔的层数一样,最多是3。刚开始里面是3个空。运算的时候会来回倒腾它。

locals=3,表示 这个栈帧的 局部变量表的局部变量空间是3个Slot ,Slot是局部变量表的最小单元, 一个可以存储32位大小的数据。直白来说就是现在它能存3个变量值到这里面。一般是从索引 1开始取数,不是0没有,而是被关键字“this”占用了,具体的再找时间啰嗦。

画个图,感受一下:

1.初始状态

stack=3, locals=3, args_size=1

  1. 执行指令 bipush ,作用是将单字节的整数常量值(-128 - 127 )推入操作数栈顶,跟随有一个参数,指明推送的常量值,这里是100,那就是这样了呗:

  1. 执行指令 istore_1 ,作用是 将操作数栈顶的整型值出栈 并存放到 局部变量表的 索引为1 的Slot 中。如图:

4.执行指令 sipush , 作用和 bipush 差不多,就是 把指定的参数值(这里是200) 入操作数栈,如图:

5.执行指令 istore_2 , 作用和 istore_1 一样 , 就是 把操作数栈栈顶的数值 出栈,并存放到 局部变量表 的索引为2 的Slot 中。如图:

6.接着两条 load 指令,iload_2 , iload_1 这两个的作用是 把Slot2 和 Slot1 的数组复制一份 放入 到操作数栈中。那就变成如图:

OK ,看到了吗,现在 a 和 b 其实都有了两份在内存里,一个在操作数栈里,一个在局部变量表里。好吧,继续看我们的指令,看到最后会是什么结果。

7.执行指令 dup , 这个指令什么意思呢?意思是 “复制栈顶一个或者两个数值并将复制值重新压入栈顶” ,那就是如图。复制不了两个,只能复制一个,深度最大才是3嘛。而且 dup 就是复制一个。

8.执行指令 istore_2 , 这个指令,上面讲过,把 栈顶数据 出栈,存入 Slot2 ,那就是如图:

看,现在 b 的这个位置变成 100了,好像有点意思了,那怎么把 a 变成 200 呢,继续往下看。

9.执行指令 iconst_0 ,这个指令的意思是 取 一个 -1 ~ 5 之间的常量,并压入操作数栈,取谁呢,给的参数是谁,就取谁,取 0 。那就变成这样:

  1. 执行指令 imul ,这个指令的意思是 乘法指令,是对两个操作数栈上的值进行乘法运算,并把结果重新压入栈中。那就变成这样:(0 * 100 = 0)

11.执行指令 iadd ,这个指令的意思是 加法指令 ,是对两个操作数栈上的值进行 加法运算,并把结果重新压入栈中。那就变成这样:(0 + 200 = 200)

12.执行指令 istore_1 ,这个指令上面讲过了,把栈顶数据出栈,并存放到Slot1中。如图:

到这一步,基本上,已经可以看出来a 是怎么变成 200 的了。那我们继续往下再看看,还有啥。

13.再看最后一张图吧:

大意是 ,把 Slot1 的值给 a , 把Slot 的值给 b, 输出之后,祭出来我们的第一张图:

【完】


看似很简单的一个表达式,结果折磨了我两天,才稍微捋出来个大概,真是太菜了。其实问题的答案都已经写在书里了,有兴趣的小伙伴可以翻一翻 《深入理解Java虚拟机》 的第六章 和 第八章,肯定会有所收获。感谢群里积极回答我提问的 @万籁,@小白,@明哥的舔狗,明哥是个小可耐 等大佬。还有我牛逼的同事给我的一番解惑操作。

后面我会再多找一些有意思的表达式,继续深入 理解一下Java虚拟机的运行过程,有兴趣的小伙伴可以关注我,一块讨论,一块嗨皮~

若有错漏,欢迎指正!

JVM对算术运算做了什么??的更多相关文章

  1. MDX

    简介 把md文件里的图片转成base64,方便发给别人和上传博客园等博客平台 初衷 用Typora写markdown的感觉很爽,但是每当我写好一篇文章,想要发给小伙伴们炫耀炫耀,或者上传博客园,CSD ...

  2. Jvm 内存浅析 及 GC个人学习总结

    从诞生至今,20多年过去,Java至今仍是使用最为广泛的语言.这仰赖于Java提供的各种技术和特性,让开发人员能优雅的编写高效的程序.今天我们就来说说Java的一项基本但非常重要的技术内存管理 了解C ...

  3. JVM学习(3)——总结Java内存模型

    俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及到的知识点总结如下: 为什么学习Java的内存模式 缓存一致性问题 什么是内存模型 JMM(Java Memory Model)简 ...

  4. 【目录】JVM目录

    JVM学习目录 为了方便园友,现对JVM序列笔记做了归档,园友们可以一口气读完整个JVM的笔记 1. [JVM]JVM系列之JVM体系(一) 2. [JVM]JVM系列之垃圾回收(二) 3. [JVM ...

  5. JVM原理和优化

    JVM工作原理和特点主要是指操作系统装入JVM是通过jdk中Java.exe来完成,通过下面4步来完成JVM环境. 1.创建JVM装载环境和配置 2.装载JVM.dll 3.初始化JVM.dll并挂界 ...

  6. jvm 原理和优化

    在csdn 上看到的,觉得很好,收藏了: 原博文地址: 濤子 http://blog.csdn.net/ning109314/article/details/10411495/ JVM工作原理和特点主 ...

  7. 意译:《JVM Internals》

    译者语 为加深对JVM的了解和日后查阅时更方便,于是对原文进行翻译.内容是建立在我对JVM的认识的基础上翻译的,加上本人的英语水平有限,若有纰漏请大家指正,谢谢. 原文地址:http://blog.j ...

  8. 基础03 JVM到底在哪里?

    1.Java是编译型语言还是解释型语言? 是解释型定义: 编译型语言:把做好的源程序全部编译成二进制代码的可运行程序.然后,可直接运行这个程序. 解释型语言:把做好的源程序翻译一句,然后执行一句,直至 ...

  9. Java之美[从菜鸟到高手演变]之JVM内存管理及垃圾回收

    很多Java面试的时候,都会问到有关Java垃圾回收的问题,提到垃圾回收肯定要涉及到JVM内存管理机制,Java语言的执行效率一直被C.C++程序员所嘲笑,其实,事实就是这样,Java在执行效率方面确 ...

随机推荐

  1. 7.SortSet排序集合类型操作

    Sort Set排序集合类型 (1)介绍 和set一样sorted set也是string类型元素的集合,不同的是每个元素都会关联一个权.通过权值可以有序的获取集合中的元素 该Sort Set类型适合 ...

  2. leetcode198之打家劫舍问题

    问题描述: 你是一个专业的小偷,计划偷窃沿街的房屋.每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警. 给 ...

  3. 一、【python】机器学习基础

    专有名词 机器学习 (machine learning) 预测分析 (predictive analytics) 统计学习 (statistical learning) 监督学习 (supervise ...

  4. NET-NTLM hash传递

    net-ntlm无法进行hash直接传递,通过responder等中继器拿到的net-ntlm破解也很难,所以利用responder加MultiRelay获取一直存在的shell. 注意的一点是: N ...

  5. Docker: GUI 应用,Ubuntu 上如何运行呢?

    操作系统: Ubuntu 18.04 运行镜像: continuumio/anaconda3, based on debian Step 1) 安装 Docker # update the apt p ...

  6. PowerPC-object与elf中的符号引用

    https://mp.weixin.qq.com/s/6snzjEpDT4uQuCI2Nx9VcQ   一. 符号引用 编译会先把每个源代码文件编译成object目标文件,然后把所有目标文件链接到一起 ...

  7. Rocket - spec - RISC-V规范整理

    https://mp.weixin.qq.com/s/xP8JRhkmgUQf0QRm3S2mjA   根据RISC-V规范整理的几个文档.   ​​     1. 原文链接 https://risc ...

  8. jchdl - RTL实例 - MOS6502 CPU

    https://mp.weixin.qq.com/s/OguQKMU64GGdinCJjgyeKw   实现MOS6502 CPU,主要是实现状态机.   参考链接 https://github.co ...

  9. IntelliJ IDEA连接不上数据库 (Connection to testdb@localhost failed. [08001] Could not create connection to database server. Attempted reconnect 3 times. Giving up.)

    问题提示为: 原因:MySQL数据库版本为8.0以上,需要在URL加上时区,即加上?serverTimezone=GMT 成功后为:

  10. Java实现 LeetCode 673 最长递增子序列的个数(递推)

    673. 最长递增子序列的个数 给定一个未排序的整数数组,找到最长递增子序列的个数. 示例 1: 输入: [1,3,5,4,7] 输出: 2 解释: 有两个最长递增子序列,分别是 [1, 3, 4, ...