📚《深入浅出JVM字节码》
《Java虚拟机字节码:从入门到实战》的开源版本。作者通过自己的实战经验,整合出一套适合新手的高效学习教程。归纳并提炼知识点,制定合理路线,帮助读者更快掌握核心技术。
Java语言提供的循环语句包括for、while和do-while,由于do-while不常用,因此本章不做介绍。Java循环语句的底层字节码实现实际上与条件分支语句的实现差不多,都是通过条件跳转指令完成。
我们通过一个简单的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循环在字节码层面的实现,案例代码如下。
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虚拟机字节码从入门到实战》的第二版),未经作者许可,禁止转载!
📚目录
订阅
订阅新文章发布通知吧,不错过精彩内容!
输入邮箱,提交后我们会给您发送一封邮件,您需点击邮件中的链接完成订阅设置。