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

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

内容简介:相关实例均使用Oracle JDK 1.8编译,并使用javap生成字节码指令清单。Java虚拟机通常基于操作数栈进行算术运算。只有iinc指令例外,它直接对局部变量进行自增操作。以上指令,并没有出现取反的指令操作。因为JVM并没有提供取反指令,而是使用异或指令来实现取反。

相关实例均使用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指令。


以上所述就是小编给大家介绍的《JVM指令分析实例二(算术运算、常量池、控制结构)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

TCP/IP详解 卷1:协议

TCP/IP详解 卷1:协议

W.Richard Stevens / 范建华 / 机械工业出版社 / 2000-4-1 / 45.00元

《TCP/IP详解卷1:协议》是一本完整而详细的TCP/IP协议指南。描述了属于每一层的各个协议以及它们如何在不同操作系统中运行。作者W.Richard Stevens用Lawrence Berkeley实验室的tcpdump程序来捕获不同操作系统和TCP/IP实现之间传输的不同分组。对tcpdump输出的研究可以帮助理解不同协议如何工作。 《TCP/IP详解卷1:协议》适合作为计算机专业学......一起来看看 《TCP/IP详解 卷1:协议》 这本书的介绍吧!

html转js在线工具
html转js在线工具

html转js在线工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具