`

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指令。

维基百科对NaN的定义

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

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



 

参考

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

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

 

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

 

个人公众号

二进制之路

 

  • 大小: 16.2 KB
  • 大小: 13.7 KB
  • 大小: 10.2 KB
  • 大小: 15.4 KB
  • 大小: 10.4 KB
  • 大小: 4.4 KB
  • 大小: 14.8 KB
0
0
分享到:
评论

相关推荐

    JVM指令手册_jvm指令手册_

    3. **算术运算指令**:包括加减乘除等基本数学运算,如`iadd`表示整数加法,`imul`表示整数乘法,这些指令在执行计算时会使用操作数栈。 4. **类型转换指令**:Java是一种强类型语言,不同数据类型的转换需要特定...

    JVM指令码表.zip

    2. **算术运算指令**:如`iadd`进行整数加法,`imul`进行乘法,`idiv`执行除法,还有浮点数运算指令`fadd`、`fmul`、`fdiv`等。 3. **类型转换指令**:如`i2d`将整型值转换为双精度浮点数,`checkcast`用于类型检查...

    JVM指令集 PDF 下载

    2. **算术运算指令**:JVM提供了一系列的指令进行基本的算术运算,如`iadd`进行整数加法,`imul`执行乘法,`idiv`进行除法运算等。 3. **类型转换指令**:由于Java支持多种数据类型,转换指令如`i2l`用于将整型值...

    JVM指令集.docx

    Java虚拟机(JVM)指令集是Java编程语言在运行时执行的基本操作单元,它构成了Java字节码的基础。这些指令允许JVM执行程序并管理内存。以下是对JVM指令集的一些关键点的详细说明: 1. **常量压入栈的指令**:这些...

    JVM指令

    - **算术运算指令**:如`iadd`用于执行整数加法,`fdiv`用于执行浮点数除法。 - **类型转换指令**:如`i2d`将整型数据转换为双精度浮点数,`checkcast`用于类型检查转换。 - **控制流指令**:如`goto`实现无条件跳转...

    JVM图解-JVM指令-JVM原型图.rar

    - JVM指令是字节码,每条指令对应一个特定的操作,如加载和存储变量、算术运算、控制流程、对象创建和方法调用等。 - 指令手册会列出所有这些指令,比如`iconst_5`表示将整数5压入操作数栈,`aload_0`用于将局部...

    JVM指令手册.pdf

    7. **算术运算系列** 包括加法、减法、乘法、除法等指令,如 `iadd`、`isub`、`imul`、`idiv` 等,它们对栈顶的两个int型数值进行运算,并将结果压回栈顶。 8. **类型转换系列** 如 `i2l`、`i2f`、`i2d` 用于将...

    00-JVM指令手册.zip

    - **类操作指令**:如`ldc`(加载常量池中的内容)、`checkcast`(类型检查)等,与类加载和类型检查有关。 - **方法调用指令**:如`invokevirtual`(调用实例方法)、`invokespecial`(调用构造函数或私有方法)...

    Java-JVM指令手册(2022最新版,中文全解析)

    每条JVM指令都会对操作数栈进行操作,如入栈、出栈、做算术运算等。 7. **指令集**:JVM有一套完整的指令集,包括加载/存储指令、算术逻辑指令、控制流指令、对象处理指令、类型检查和转换指令等。例如: - **a...

    JVM.rar_jvm_虚拟机

    2. **算术运算指令**:包括`iadd`(加法)、`isub`(减法)、`imul`(乘法)、`idiv`(除法)和`irem`(取余)。这些指令处理整数运算,对浮点数有对应的`fadd`、`fsub`等。 3. **类型转换指令**:如`i2d`用于将...

    深入理解JVM.

    JVM的指令集包括约248个字节码,每个字节码对应一种运算,如加载和存储变量、算术运算、控制流程、对象创建和方法调用等。JVM通过解释器将字节码转化为机器码执行,或者使用即时编译器(JIT)将热点代码编译为更高效...

    JVM与性能优化知识点的整理.docx

    2. **字节码指令**:Java字节码是一种二进制指令集,包括加载和存储指令、运算或算术指令、类型转换指令、创建类实例和数组的指令、访问字段和数组的指令,以及检查类实例类型的指令和操作数栈管理指令等。这些指令...

    mini-jvm使用 Java 8 实现 jvm

    mini-jvm实现了一套精简的指令集,涵盖了基本的算术运算、逻辑运算、控制流程和数据操作。每条指令对应一个操作,如加法、减法、跳转等。这些指令的实现涉及对内存的读写以及状态机的更新,是整个JVM的核心部分。 ...

    JVM虚拟机深度讲解

    每条字节码对应一个操作,如加载、存储、算术运算、跳转等。JIT(Just-In-Time)编译器将频繁执行的热点代码编译为本地机器码,提高运行效率。 5. 线程调度:JVM支持多线程并发执行,线程调度包括抢占式和协作式两...

    【Java毕业设计】zvm-jvm,使用java完成对jvm的设计,这个也是我的毕业设计的初稿.zip

    zvm-jvm需要实现这些指令,包括基本的算术运算、逻辑运算、控制流程、对象操作等。 5. 内存管理:JVM的垃圾回收机制负责自动管理内存,包括对象的分配和释放。zvm-jvm需要模拟这个过程,可能包括引用计数、标记-...

    JVM与性能优化--知识点整理.pdf

    - **Class类的本质**:类文件本质上是一个二进制文件,包含了类的名称、属性、方法、常量池等信息,这些信息由特定的字节码指令表示。 2. **字节码指令**:Java字节码是一系列基于16位的指令,包括加载和存储指令...

    jvm-java字节码规则.pdf

    - 还有许多其他指令用于执行算术运算、逻辑运算、比较、跳转、类型转换等。例如,`add`指令用于两个数值相加,`if_icmpne`用于比较两个int值是否不相等并进行条件跳转,`invokevirtual`用于调用实例方法,`new`用于...

    开源项目-zxh0-jvm.go.zip

    - **指令集实现**:实现JVM的指令集,包括算术运算、控制流程、对象操作等。 - **垃圾回收**:虽然Go语言有自己的垃圾回收机制,但项目可能包含针对JVM特定的内存管理策略。 - **异常处理**:模拟JVM的异常处理...

Global site tag (gtag.js) - Google Analytics