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在执行效率方面确 ...
 
随机推荐
- CF652E Pursuit For Aritifacts
			
题目传送门 这是一道很好的练习强联通的题目. 首先,从题中可以看到,题目的要求就是要我们求出从起点到终点是否可以经过flag = 1 的边. 由于是无向图,且要求很多,直接暴力dfs会很凌乱. 那么, ...
 - [256个管理学理论]001.蝴蝶效应(Butterfly Effect)
			
蝴蝶效应(Butterfly Effect) 来自于大洋彼岸的让你看不懂的解释: 蝴蝶效应是指在一个动力系统中,初始条件下微小的变化能带动整个系统的长期的巨大的连锁反应,是一种混沌的现象.“蝴蝶效应” ...
 - PreparedStatement实现针对不同表的通用查询操作
			
PreparedStatement实现针对不同表的通用查询操作:查询一样和多行 PreparedStatementQueryTest package com.aff.PreparedStatement ...
 - 缓冲区(Buffer)的数据存取
			
缓冲区(Buffer) 1. 缓冲区(Buffer):一个用于特定基本数据类 型的容器. 由 java.nio 包定义的,所有缓冲区 都是 Buffer 抽象类的子类.2. Java NIO 中的 B ...
 - Rocket - debug - TLDebugModule
			
https://mp.weixin.qq.com/s/EhUb1z5oiIw6dJ-90ifDJA 简单介绍TLDebugModule中的实现. 1. device device是一个设备描述符,包含 ...
 - Java实现 蓝桥杯VIP 算法训练 确定元音字母位置
			
算法训练 确定元音字母位置 时间限制:1.0s 内存限制:512.0MB 输入一个字符串,编写程序输出该字符串中元音字母的首次出现位置,如果没有元音字母输出0.英语元音字母只有'a'.'e'.'i'. ...
 - Java实现 LeetCode 149 直线上最多的点数
			
149. 直线上最多的点数 给定一个二维平面,平面上有 n 个点,求最多有多少个点在同一条直线上. 示例 1: 输入: [[1,1],[2,2],[3,3]] 输出: 3 解释: ^ | | o | ...
 - Java实现 LeetCode 148 排序链表
			
148. 排序链表 在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序. 示例 1: 输入: 4->2->1->3 输出: 1->2->3-> ...
 - Java实现最大连续乘积子数组
			
1 问题描述 给定一个浮点数组,任意取出数组中的若干个连续的数相乘,请找出其中乘积最大的子数组. 2 解决方案 2.1 蛮力法 该方法的时间复杂度为O(n^2). package com.liuzhe ...
 - Java实现二分图的最大权匹配
			
1 问题描述 何为二分图的最大权匹配问题? 最大权二分匹配问题就是给二分图的每条边一个权值,选择若干不相交的边,得到的总权值最大. 2 解决方案 解决这个问题可以用KM算法.理解KM算法需要首先理解& ...