JVM对算术运算做了什么??
java可以进行数字的加减乘除,但是JVM的运算步骤是什么样子呢?从一个神奇的式子入手,研究下JVM到底做了什么?
先看下图:
简直亮瞎了我的钛合金狗眼,刚看到这个式子的时候,我百思不得其解,最后在群友们和一个同事的解释下,才明白,这和JVM对算术运算的操作原理有关系,其实就是一个入栈和出栈的操作过程。
如果不明白底层的栈操作,乍一看这个式子,简直就像是突破了自己的常识,就算是运算符的优先级,那也是这样:
- 第一步,计算 (b = a),那么 b此时已经是100
- 第二步,计算 (b = a) * 0 = 100 * 0 = 0
- 第三步,计算 b + (b = a) * 0 = 100 + 0 = 100
- 第四步,把 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
- 执行指令 bipush ,作用是将单字节的整数常量值(-128 - 127 )推入操作数栈顶,跟随有一个参数,指明推送的常量值,这里是100,那就是这样了呗:
- 执行指令 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 。那就变成这样:
- 执行指令 imul ,这个指令的意思是 乘法指令,是对两个操作数栈上的值进行乘法运算,并把结果重新压入栈中。那就变成这样:(0 * 100 = 0)
11.执行指令 iadd ,这个指令的意思是 加法指令 ,是对两个操作数栈上的值进行 加法运算,并把结果重新压入栈中。那就变成这样:(0 + 200 = 200)
12.执行指令 istore_1 ,这个指令上面讲过了,把栈顶数据出栈,并存放到Slot1中。如图:
到这一步,基本上,已经可以看出来a 是怎么变成 200 的了。那我们继续往下再看看,还有啥。
13.再看最后一张图吧:
大意是 ,把 Slot1 的值给 a , 把Slot 的值给 b, 输出之后,祭出来我们的第一张图:
【完】
看似很简单的一个表达式,结果折磨了我两天,才稍微捋出来个大概,真是太菜了。其实问题的答案都已经写在书里了,有兴趣的小伙伴可以翻一翻 《深入理解Java虚拟机》 的第六章 和 第八章,肯定会有所收获。感谢群里积极回答我提问的 @万籁,@小白,@明哥的舔狗,明哥是个小可耐 等大佬。还有我牛逼的同事给我的一番解惑操作。
后面我会再多找一些有意思的表达式,继续深入 理解一下Java虚拟机的运行过程,有兴趣的小伙伴可以关注我,一块讨论,一块嗨皮~
若有错漏,欢迎指正!
JVM对算术运算做了什么??的更多相关文章
- MDX
简介 把md文件里的图片转成base64,方便发给别人和上传博客园等博客平台 初衷 用Typora写markdown的感觉很爽,但是每当我写好一篇文章,想要发给小伙伴们炫耀炫耀,或者上传博客园,CSD ...
- Jvm 内存浅析 及 GC个人学习总结
从诞生至今,20多年过去,Java至今仍是使用最为广泛的语言.这仰赖于Java提供的各种技术和特性,让开发人员能优雅的编写高效的程序.今天我们就来说说Java的一项基本但非常重要的技术内存管理 了解C ...
- JVM学习(3)——总结Java内存模型
俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及到的知识点总结如下: 为什么学习Java的内存模式 缓存一致性问题 什么是内存模型 JMM(Java Memory Model)简 ...
- 【目录】JVM目录
JVM学习目录 为了方便园友,现对JVM序列笔记做了归档,园友们可以一口气读完整个JVM的笔记 1. [JVM]JVM系列之JVM体系(一) 2. [JVM]JVM系列之垃圾回收(二) 3. [JVM ...
- JVM原理和优化
JVM工作原理和特点主要是指操作系统装入JVM是通过jdk中Java.exe来完成,通过下面4步来完成JVM环境. 1.创建JVM装载环境和配置 2.装载JVM.dll 3.初始化JVM.dll并挂界 ...
- jvm 原理和优化
在csdn 上看到的,觉得很好,收藏了: 原博文地址: 濤子 http://blog.csdn.net/ning109314/article/details/10411495/ JVM工作原理和特点主 ...
- 意译:《JVM Internals》
译者语 为加深对JVM的了解和日后查阅时更方便,于是对原文进行翻译.内容是建立在我对JVM的认识的基础上翻译的,加上本人的英语水平有限,若有纰漏请大家指正,谢谢. 原文地址:http://blog.j ...
- 基础03 JVM到底在哪里?
1.Java是编译型语言还是解释型语言? 是解释型定义: 编译型语言:把做好的源程序全部编译成二进制代码的可运行程序.然后,可直接运行这个程序. 解释型语言:把做好的源程序翻译一句,然后执行一句,直至 ...
- Java之美[从菜鸟到高手演变]之JVM内存管理及垃圾回收
很多Java面试的时候,都会问到有关Java垃圾回收的问题,提到垃圾回收肯定要涉及到JVM内存管理机制,Java语言的执行效率一直被C.C++程序员所嘲笑,其实,事实就是这样,Java在执行效率方面确 ...
随机推荐
- Linux(二):VMware虚拟机中Ubuntu安装详细过程
Linux(二):VMware虚拟机中Ubuntu安装详细过程 目录 1 准备 2 安装 2.1 虚拟机的建立 2.2 虚拟机安装Ubuntu系统 2.3 虚拟机设置 3 完成 1 准备 1.操作系统 ...
- Chisel3 - util - LFSR16
https://mp.weixin.qq.com/s/DSdb4tmRwDTOki7mbyuu9A 实现16位线性反馈移位寄存器.可用于生成简单的伪随机数. 参 ...
- html css javascript实现弹弹球
效果如图: 原创代码: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> & ...
- JavaScript (三) js的基本语法 - - 数组 与函数 、关键字
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 一.关键字 1.break 关键字使用 break关键字:如果在循环中使用,遇到了break,则立刻跳出 ...
- 这才是你需要的最基础的.Net基础面试题(通俗易懂,最基础的.Net)2
51. 委托回调静态方法和实例方法有何区别? 当一个实例方法被调用时,需要通过实例对象来访问,绑定一个实例方法到委托必须同时让委托得到实例方法的代码段和实例对象的信息,这样在委托被回调时候.NET才能 ...
- Java实现 LeetCode 741 摘樱桃(DFS || 递推 || 传纸条)
741. 摘樱桃 一个N x N的网格(grid) 代表了一块樱桃地,每个格子由以下三种数字的一种来表示: 0 表示这个格子是空的,所以你可以穿过它. 1 表示这个格子里装着一个樱桃,你可以摘到樱桃然 ...
- (Java实现) 零件分组
零件分组(Stick)-动态规划-中高级 Case Time Limit:1000MS Time Limit: 3000MS Memory Limit: 65536K Total Submission ...
- Java实现 LeetCode 501 二叉搜索树中的众数
501. 二叉搜索树中的众数 给定一个有相同值的二叉搜索树(BST),找出 BST 中的所有众数(出现频率最高的元素). 假定 BST 有如下定义: 结点左子树中所含结点的值小于等于当前结点的值 结点 ...
- Java实现 LeetCode 488 祖玛游戏
488. 祖玛游戏 回忆一下祖玛游戏.现在桌上有一串球,颜色有红色,黄色(Y),蓝色(B),绿色(G),还有白色(W). 现在你手里也有几个球. 每一次,你可以从手里的球选一个,然后把这个球插入到一串 ...
- Java实现 蓝桥杯VIP 算法训练 链表数据求和操作
算法训练 9-7链表数据求和操作 时间限制:1.0s 内存限制:512.0MB 读入10个复数,建立对应链表,然后求所有复数的和. 样例输入 1 2 1 3 4 5 2 3 3 1 2 1 4 2 2 ...