内容简介:相关实例均使用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并没有提供取反指令,而是使用异或指令来实现取反。
对一个数进行取反,相当于该数的二进制每一位与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;
}
复制代码
字节码指令序列
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++;
}
}
复制代码
字节码指令序列
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++;
}
}
复制代码
字节码指令序列
由于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;
}
}
复制代码
字节码指令序列
if实例2
int greaterThan100(double d) {
if (d > 100.0) {
return 1;
} else {
return -1;
}
}
复制代码
字节码指令序列
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指令分析实例二(算术运算、常量池、控制结构)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- JVM指令分析实例二(算术运算、常量池、控制结构)
- Python算术运算符
- Python算术运算符
- Golang语言基础教程:算术运算符
- Java中的int vs float算术效率
- [译] 详解 Python 的二元算术运算,为什么说减法只是语法糖?
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
ActionScript 3.0 Cookbook
Joey Lott、Darron Schall、Keith Peters / Adobe Dev Library / 2006-10-11 / GBP 28.50
Well before Ajax and Microsoft's Windows Presentation Foundation hit the scene, Macromedia offered the first method for building web pages with the responsiveness and functionality of desktop programs......一起来看看 《ActionScript 3.0 Cookbook》 这本书的介绍吧!