JVM指令分析实例二(算术运算、常量池、控制结构)

栏目: Java · 发布时间: 6年前

内容简介:相关实例均使用Oracle JDK 1.8编译,并使用javap生成字节码指令清单。Java虚拟机通常基于操作数栈进行算术运算。只有iinc指令例外,它直接对局部变量进行自增操作。

相关实例均使用Oracle JDK 1.8编译,并使用javap生成字节码指令清单。

算术运算

Java虚拟机通常基于操作数栈进行算术运算。只有iinc指令例外,它直接对局部变量进行自增操作。

实例代码

int align2agrain(int i, int grain) {
return ((i + grain - 1) & ~(grain - 1));
}

字节码指令序列

JVM指令分析实例二(算术运算、常量池、控制结构)

以上指令,并没有出现取反的指令操作。因为JVM并没有提供取反指令,而是使用异或指令来实现取反。

对一个数进行取反,相当于该数的二进制每一位与1进行异或操作。由于-1的补码二进制表示为全部都是1,因此对一个数进行取反,也相当于-1与该数进行异或。

-1的原码、反码和补码表示

[10000001]原=[11111110]反=[11111111]补

异或实现取反

~x = -1^x

访问运行时常量池

实例代码

void useManyNumeric() {
int i = 100;
int j = 1000000;
long l1 = 1;
long l2 = 0xffffffff;
double d = 2.2;
}

字节码指令序列

JVM指令分析实例二(算术运算、常量池、控制结构)

ldc、ldc_w:将int、float或String类型常量值从常量池中推送至栈顶。

ldc2_w:将long、double类型常量值从常量池中推送至栈顶。(只有宽索引版本)

其中,ldc_w和ldc2_w属于宽索引指令,即指令对应的(索引值)参数为2个字节。而ldc指令对应的(索引值)参数为1个字节。

当运行时常量池中的常量个数超过256个(1个字节所能代表的数量)时,需要使用支持2个字节索引值的指令ldc_w指令来代替ldc访问常量池

在局部变量表中,long和double类型的数据占用两个连续的局部变量,并且采用两个局部变量中较小的索引值来定位其数据

因此,lstore_3、lstore 5、dstore 7 这三个指令实际存入的局部变量索引号分别为3和4、5和6、7和8。(局部变量表的索引值从0开始)

控制结构

Java虚拟机会根据数据类型的变化来生成不同的条件跳转语句。

while实例1

void whileInt() {
int i = 0;
while (i < 100) {
i++;
}
}

字节码指令序列

JVM指令分析实例二(算术运算、常量池、控制结构)

iinc用于实现局部变量的自增操作。在所有字节码指令中,只有该指令可直接用于操作局部变量。

对于循环的实现, 将条件判断放在循环的最前面不是更易于理解,为什么要放在最后面 ?让我们来看看放在最前面的指令序列:

2 iload_1
3 bipush 100
5 if_icmpge 14
8 iinc 1,1
11 goto 2

显然,两种实现方式第1次循环都要执行5条执行。但对于后续的循环,前者只需要执行4条指令,而后者则需要执行5条指令。因此, 将条件判断放在循环的最后面可以更高效的执行循环

while实例2

void whileDouble() {
double i = 0;
while (i < 100.1) {
i++;
}
}

字节码指令序列

JVM指令分析实例二(算术运算、常量池、控制结构)

由于iinc只针对int类型的局部变量进行自增操作,JVM并没有提供相应的指令来操作double类型。因此,需要借助dadd来实现double类型的自增操作。

同样,对于数值类型,以if开头的比较跳转指令,都只支持int类型(对于非数值类型,if比较跳转指令还支持引用类型数值)。因此,JVM另外提供了dcmpg、dcmpl来比较两个double类型数值的大小,然后将比较结果(1,0,-1)压入栈顶。最后,再使用int类型的if判断指令来进行判断跳转。

dcmpg与dcmpl的区别仅在于,当比较的其中一个值为NaN时,dcmpg将1压入栈顶,而dcmpl将-1压入栈顶。

ldc相关指令都是将常量值从常量池中推至栈顶,前面"访问运行时常量池"一节已经介绍过了。

对于for循环分析,请看第一篇: JVM指令分析实例一(常量、局部变量、for循环)

if实例1

int lessThan100(double d) {
if (d < 100.0) {
return 1;
} else {
return -1;
}
}

字节码指令序列

JVM指令分析实例二(算术运算、常量池、控制结构)

if实例2

int greaterThan100(double d) {
if (d > 100.0) {
return 1;
} else {
return -1;
}
}

字节码指令序列

JVM指令分析实例二(算术运算、常量池、控制结构)

if实例2与if实例1的差别仅在于比较符号由小于号改为大于号,因此ifge指令也相应的变成ifle指令。

如果细心一点,还会发现一个差异,double比较指令由dcmpg变成了dcmpl。

那么,JVM在什么情况下使用dcmpg,什么情况下又会使用dcmpl呢 ?为了理解这一点,我们需要先回顾一下浮点数中的NaN值。

Java虚拟机关于浮点数的规范

浮点类型包含float和double类型两种,32位单精度和64位双精度与IEEE 754格式的取值与操作是一致的。

NaN值用于表示某此无效的运算操作,例如0除以0等情况。

只要有操作数是NaN,那么对它进行任何数值比较和等值测试都会返回false。任何数值与NaN进行不等值比较都会返回true。

有了以上知识,我们再回到例子来分析一下。

我们知道,dcmpg与dcmpl的作用都是比较两个double类型数值的大小,并将结果(1,0,-1)压入栈顶。区别仅在于,当比较的其中一个值为NaN时,dcmpg将1压入栈顶,而dcmpl将-1压入栈顶。

对于 if (d < 100.0) {},隐含了两个条件,一个是d必须小于100.0,另一个是d不能为NaN(如果为NaN会返回false)。因此,NaN属于该条件之外的情况。

当 if (d < 100.0) {} 成立时,执行比较指令之后结果为-1。由于满足该条件时d不能为NaN,显然当d为NaN时比较结果不能为-1。因此比较指令排除dcmpl,只能使用dcmpg指令。

维基百科对NaN的定义

NaN( Not a Number ,非数)是计算机科学中数值数据类型的一类值,表示 未定义或不可表示的值 。常在浮点数运算中使用。首次引入NaN的是1985年的IEEE 754浮点数标准。

返回NaN的运算有如下三种:

JVM指令分析实例二(算术运算、常量池、控制结构)

参考

《Java虚拟机规范》(Java SE 8版)

NaN:https://zh.wikipedia.org/wiki/NaN

转载请注明来源:http://zhanjia.iteye.com/blog/2431234

个人公众号

二进制之路

JVM指令分析实例二(算术运算、常量池、控制结构)


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Machine Learning

Machine Learning

Kevin Murphy / The MIT Press / 2012-9-18 / USD 90.00

Today's Web-enabled deluge of electronic data calls for automated methods of data analysis. Machine learning provides these, developing methods that can automatically detect patterns in data and then ......一起来看看 《Machine Learning》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

SHA 加密
SHA 加密

SHA 加密工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具