MENU

Java杂记(三)关于赋值与自增运算的理解

June 16, 2018 • Code

前言:通过反编译 class 文件,对比赋值与自增运算操作执行顺序,加深对二者的理解。

主要过程

部分虚拟机执行指令

下面所提及的 指令,均指 JVM 指令,并非实际的 CPU 指令

操作栈索引从 1 开始,局部变量表从 0 开始。如果是非静态方法,则默认局部变量表中第 0 号索引对应 this 引用

  • iconst_1:将常量 1 压入操作数栈。

  • istore_1:将操作数栈栈顶元素弹出,写入局部变量表索引为 1 的位置。

  • iload_1:将局部变量表索引为 1 的元素压入栈顶。

  • iadd:将操作数栈栈顶两个元素弹出相加,结果压入栈顶。

  • iinc 1, 1:将局部变量表索引为1的值增加1,没有经过栈,直接在局部变量表中增加 1。

栗子

栗子 1

详细过程:

0. iconst_1:将常量 1 压入栈。

1. istore_1:栈顶元素存入局部变量表索引为 1 的位置。(赋值

2. iload_1:局部变量表索引为 1 的值压入栈顶。

3. iconst_1:将常量 1 压入栈。

4. iadd:将操作数栈栈顶两个元素弹出相加,结果压入栈顶。(相加

5. istore_1:栈顶元素存入局部变量表索引为 1 的位置。(赋值

/**
 * Code:
 *       stack=2, locals=2, args_size=1
 *          0: iconst_1
 *          1: istore_1
 *          2: iload_1
 *          3: iconst_1
 *          4: iadd
 *          5: istore_1
 *          6: return
 */
public void test1() {
    int i = 1;
    i = i + 1;
}

下述的栗子皆与此相似。

栗子 2

/**
 * Code:
 *       stack=1, locals=2, args_size=1
 *          0: iconst_1
 *          1: istore_1
 *          2: iinc          1, 1
 *          5: return
 */
public void test2() {
    int i = 1;
    i++;
}

栗子 3

/**
 * Code:
 *       stack=1, locals=2, args_size=1
 *          0: iconst_1
 *          1: istore_1
 *          2: iload_1
 *          3: iinc          1, 1
 *          6: istore_1
 *          7: return
 */
public void test3() {
    int i = 1;
    i = i++;
}

栗子 4

/**
 * Code:
 *       stack=2, locals=2, args_size=1
 *          0: iconst_1
 *          1: istore_1
 *          2: iload_1
 *          3: iconst_1
 *          4: iadd
 *          5: istore_1
 *          6: iload_1
 *          7: istore_1
 *          8: return
 */
public void test4() {
    int i = 1;
    i = i + 1;
    i = i;
}

分析

从上面栗子可知:

  • 遇到常量,先将常量压入栈,再从栈顶取出,存入局部变量表。

  • 遇到赋值运算,将赋值运算的右部(计算部分)压入栈顶进行运算,把结果写入栈顶,再从栈顶取出结果存入局部变量表(iload_*)。

  • 自增自减运算单独对应一条指令 iinc,这条指令不经过操作数栈而直接在局部变量表中进行操作,因此栗子 3 中的 i = i++ 最后结果保持不变,具体过程如下图:

小结

了解部分 Java 虚拟机的指令,注意自增运算与赋值运算可能存在 值覆盖 的问题。

Last Modified: July 5, 2018
Archives QR Code
QR Code for this page
Tipping QR Code