📚《深入浅出JVM字节码》

《Java虚拟机字节码:从入门到实战》的开源版本。作者通过自己的实战经验,整合出一套适合新手的高效学习教程。归纳并提炼知识点,制定合理路线,帮助读者更快掌握核心技术。


循环语句的实现

Java语言提供的循环语句包括for、while和do-while,由于do-while不常用,因此本章不做介绍。Java循环语句的底层字节码实现实际上与条件分支语句的实现差不多,都是通过条件跳转指令完成。

while循环

我们通过一个简单的while循环例子,了解while循环在字节码层面的实现,案例代码如下。

public void whileDemo() {
    int count = 10;
    while (count > 0) {
        count--;
    }
}

使用javap命令输出whileDemo方法的字节码如下。

public void whileDemo();
    Code:
       0: bipush        10
       2: istore_1
       3: iload_1
       4: ifle          13
       7: iinc          1, -1
      10: goto          3
      13: return

偏移量为0的字节码指令为bipush,该指令将立即数10放到操作数栈顶,接着使用istore_1指令将操作数栈栈顶的10存储到局部变量表索引为1的Slot,也就是给局部变量count赋值。虽然只有一个局部变量,但因为索引为0的Slot用来存储 this引用了,所以局部变量count存储在局部变量表的索引为1的Slot。

偏移量为3到10的字节码指令实现while循环。iload_1将局部变量count的值放到操作数栈栈顶,接着使用ifle条件跳转指令判断栈顶的元素是否小于等于0,如果小于等于0则跳转到偏移量为13的字节码指令,也就是结束while循环。ifle后面跟的是while循环体中的代码,iinc指令是将局部变量count减1。while循环体结束处会加上一条goto指令,goto指令是无条件跳转指令,本例中用于跳转到偏移量为3的字节码指令,直到ifle指令的条件成立才跳转到return指令结束循环。

for循环

for循环语句的一般表达式为:

for(单次表达式;条件表达式;末尾循环体){

​ 中间循环体;

}

我们通过一个简单的for循环例子,了解for循环在字节码层面的实现,案例代码如下。

public int forDemo() {
    int count = 0;
    for (int i = 1; i <= 10; i++) {
            count += i;
    }
    return count;
}

使用javap命令输出forDemo方法的字节码如下。

public int forDemo();
    Code:
       0: iconst_0
       1: istore_1
       2: iconst_1
       3: istore_2
       4: iload_2
       5: bipush        10
       7: if_icmpgt     20
      10: iload_1
      11: iload_2
      12: iadd
      13: istore_1
      14: iinc          2, 1
      17: goto          4
      20: iload_1
      21: ireturn

其中,偏移量为0、1的两条字节码指令实现为局部变量count赋值为0;偏移量为2、3的两条字节码指令实现为局部变量i赋值;偏移量为4、5、7的字节码指令判断局部变量i是否大于10,条件成立则跳转到偏移量为20的字节码指令执行;偏移量为10、11、12、13这四条字节码指令为局部变量count的值加1;偏移量为13的字节码指令给局部变量i的值加1;偏移量为17的字节码指令告诉虚拟机下一条指令的偏移量为4,即跳转到偏移量为4的字节码指令,而偏移量为4开始的连续3条指令就是判断局部变量i是否大于10的,这便是for循环的实现。

我们常用的for循环还有一种,就是forEach。forEach语句是简化版的for语句,实际上是通过编译器实现的。forEach语句的格式为:

for(元素类型 元素变量: 数组或集合对象) {

}

以使用forEach语句遍历一个集合为例,从字节码层面看forEach的实现,案例代码如下。

public void forEachDemo(List<String> list) {
    for (String str : list) {
        System.out.println(str);
    }
}

使用javap命令输出forEachDemo方法的字节码如下。

 public void forEachDemo(java.util.List<java.lang.String>);
    Code:
       0: aload_1
       1: invokeinterface #2,  1            // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
       6: astore_2
       7: aload_2
       8: invokeinterface #3,  1            // InterfaceMethod java/util/Iterator.hasNext:()Z
      13: ifeq          36
      16: aload_2
      17: invokeinterface #4,  1            // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
      22: checkcast     #5                  // class java/lang/String
      25: astore_3
      26: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      29: aload_3
      30: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      33: goto          7
      36: return

从编译后的字节码可以看出,使用forEach语句遍历一个集合,实际上是通过迭代器Iterator加while循环实现的。偏移量为0和1的字节码指令是调用局部变量list的iterator方法获取该集合的迭代器;偏移量为7和8的字节码指令调用迭代器的hasNext方法;接着偏移量为13的字节码指令ifeq判断hasNext方法的返回值是否等于0,如果hasNext方法返回false那么等式就成立,等式成立则跳转到偏移量为36的字节码指令,循环结束。

forEach语句中定义的局部变量str,是在循环体中通过调用迭代器Iterator的next方法为其赋值,为str赋值的字节码指令一定是放在我们编写的forEach循环体内的代码编译后的字节码指令之前。


发布于:2021 年 08 月 21 日
作者: 吴就业
链接: https://github.com/wujiuye/JVMByteCodeGitBook
来源: Github Pages 开源电子书《深入浅出JVM字节码》(《Java虚拟机字节码从入门到实战》的第二版),未经作者许可,禁止转载!


📚目录