硬核万字长文,深入理解 Java 字节码指令(建议收藏) (2)

举例来说。

public void pushConstLdc() {
    // 范围 [-1,5]
    int iconst = -1;
    // 范围 [-128,127]
    int bipush = 127;
    // 范围 [-32768,32767]
    int sipush= 32767;
    // 其他 int
    int ldc = 32768;
    String aconst = null;
    String IdcString = "沉默王二";
}

通过 jclasslib 看一下 pushConstLdc() 方法的字节码指令。

硬核万字长文,深入理解 Java 字节码指令(建议收藏)

iconst_m1:将 -1 入栈。范围 [-1,5]。

bipush 127:将 127 入栈。范围 [-128,127]。

sipush 32767:将 32767 入栈。范围 [-32768,32767]。

ldc #6 <32768>:将常量池中下标为 6 的常量 32768 入栈。

aconst_null:将 null 入栈。

ldc #7 <沉默王二>:将常量池中下标为 7 的常量“沉默王二”入栈。

3)将栈顶的数据出栈并装入局部变量表中

主要是用来给局部变量赋值,这类指令主要以 store 的形式存在。

xstore_(x 为 i、l、f、d、a,n 默认为 0 到 3)

xstore(x 为 i、l、f、d、a)

明白了 xload_ 和 xload,再看 xstore_ 和 xstore 就会轻松得多,作用反了一下而已。

大家来想一个问题,为什么要有 xstore_ 和 xload_ 呢?它们的作用和 xstore n、xload n 不是一样的吗?

xstore_ 和 xstore n 的区别在于,前者相当于只有操作码,占用 1 个字节;后者相当于由操作码和操作数组成,操作码占 1 个字节,操作数占 2 个字节,一共占 3 个字节。

由于局部变量表中前几个位置总是非常常用,虽然 xstore_<n> 和 xload_<n> 增加了指令数量,但字节码的体积变小了!

举例来说。

public void store(int age, String name) {
    int temp = age + 2;
    String str = name;
}

通过 jclasslib 看一下 store() 方法的字节码指令。

硬核万字长文,深入理解 Java 字节码指令(建议收藏)

istore_3:从操作数中弹出一个整数,并把它赋值给局部变量表中索引为 3 的变量。

astore 4:从操作数中弹出一个引用数据类型,并把它赋值给局部变量表中索引为 4 的变量。

通过查看局部变量表就能关联上了。

硬核万字长文,深入理解 Java 字节码指令(建议收藏)

02、算术指令

算术指令用于对两个操作数栈上的值进行某种特定运算,并把结果重新压入操作数栈。可以分为两类:整型数据的运算指令和浮点数据的运算指令。

需要注意的是,数据运算可能会导致溢出,比如两个很大的正整数相加,很可能会得到一个负数。但 Java 虚拟机规范中并没有对这种情况给出具体结果,因此程序是不会显式报错的。所以,大家在开发过程中,如果涉及到较大的数据进行加法、乘法运算的时候,一定要注意!

当发生溢出时,将会使用有符号的无穷大 Infinity 来表示;如果某个操作结果没有明确的数学定义的话,将会使用 NaN 值来表示。而且所有使用 NaN 作为操作数的算术操作,结果都会返回 NaN。

举例来说。

public void infinityNaN() {
    int i = 10;
    double j = i / 0.0;
    System.out.println(j); // Infinity

    double d1 = 0.0;
    double d2 = d1 / 0.0;
    System.out.println(d2); // NaN
}

任何一个非零的数除以浮点数 0(注意不是 int 类型),可以想象结果是无穷大 Infinity 的。

把这个非零的数换成 0 的时候,结果又不太好定义,就用 NaN 值来表示。

Java 虚拟机提供了两种运算模式

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zggffx.html