程序流程离不开条件控制,为了支持条件跳转,虚拟机提供了大量字节码指令,大体上可以分为
1)比较指令、
2)条件跳转指令、
3)比较条件跳转指令、
4)多条件分支跳转指令、
5)无条件跳转指令等。
一、条件跳转指令
1.1、说明
条件跳转指令通常和比较指令结合使用。在条件跳转指令执行前,一般可以先用比较指令进行栈顶元素的准备,然后进行条件跳转。
条件跳转指令有: ifeq, iflt, ifle, ifne, ifgt,ifge,ifnull,ifnonnull。这些指令都接收两个字节的操作数,用于计算跳转的位置(16位符号整数作为当前位置的offset)。
它们的统一含义为:弹出栈顶元素,测试它是否满足某一条件,如果满足条件,则跳转到给定位置。
具体说明:
注意:
- 与前面的运算规则一致:
- 对于boolean、byte、char、short类型的条件分支比较操作,都是使用int类型的比较指令完成
- 对于long、float、double类型的条件分支比较操作,则会先执行相应类型的比较运算指令,运算指令会返回一个整型值到操作数栈中,随后再执行int类型的条件分支比较操作来完成整个分支跳转
- 由于各类型的比较最终都会转为 int类型的比较操作,所以Java虚拟机提供的int类型的条件分支指令是最为丰富和强大的。
1.2、举例
1.2.1 例子一:数字比较
ifne 12(+9)是指是否等于0,如果等于就跳转到操作码12的位置,如果不等于就继续向下执行,goto 15(+6)是指跳转到操作码15的位置
跟不等于进行对比
1.2.2 例子二:字符串比较
1.2.3 例子三:结合比较指令
fcompg比较9.0<10.0,如果大于就压入一个-1,ifge是比较-1>=0,条件不满足,不用跳转,指令继续往下走,iconst_1往操作数栈中压入一个1,在调取invokevirual #5 PrintStream.println方法的时候它会将1转换成true后再进行输出。
跟小于做比较
两个int的数值比较在比较条件跳转指令中二、比较条件跳转指令
2.1、说明
比较条件跳转指令类似于比较指令和条件跳转指令的结合体,它将比较和跳转两个步骤合二为一。
这类指令有:ificmpeq、if_icmpne、if_icmplt、if_icmpgt、 if_icmple、if_icmpge、if_acmpeq和if_acmpne。其中指令助记符加上“if”后,以字符“i”开头的指令针对int型整数操作(也包括short和byte类型),以字符“a”开头的指令表示对象引用的比较。
具体说明:
这些指令都接收两个字节的操作数作为参数,用于计算跳转的位置。同时在执行指令时,栈顶需要准备两个元素进行比较。指令执行完成后,栈顶的这两个元素被清空,且没有任何数据入栈。如果预设条件成立,则执行跳转,否则,继续执行下一条语句。
2.2、举例
三、多条件分支跳转
3.1、多条件分支跳转指令
多条件分支跳转指令是专为switch-case语句设计的,主要有tableswitch和lookupswitch。
从助记符上看,两者都是switch语句的实现,它们的区别:
- tableswitch要求多个条件分支值是连续的,它内部只存放起始值和终止值,以及若干个跳转偏移量,通过给定的操作数index,可以立即定位到跳转偏移量位置,因此效率比较高。
- 指令lookupswitch内部存放着各个离散的case-offset对,每次执行都要搜索全部的case-offset对,找到匹配的case值,并根据对应的offset计算跳转地址,因此效率较低。
指令tableswitch的示意图如下图所示。由于tableswitch的case值是连续的,因此只需要记录最低值和最高值,以及每一项对应的offset偏移量,根据给定的index值通过简单的计算即可直接定位到offset。
指令lookupswitch处理的是离散的case值,但是出于效率考虑,将case-offset对按照case值大小排序,给定index时,需要查找与index相等的case,获得其offset,如果找不到则跳转到default。指令lookupswitch 如下图所示
3.2、举例
连续的case
如果代码case里没有break那字节码中就没有goto(无条件跳转指令)
不连续的case
代码:
字节码:
0 aload_1
1 astore_2
2 iconst_m1
3 istore_3
4 aload_2
5 invokevirtual #19 <java/lang/String.hashCode>
8 lookupswitch 4
-1842350579: 52 (+44)
-1837878353: 66 (+58)
-1734407483: 94 (+86)
1941980694: 80 (+72)
default: 105 (+97)
52 aload_2
53 ldc #20 <SPRING>
55 invokevirtual #21 <java/lang/String.equals>
58 ifeq 105 (+47)
61 iconst_0
62 istore_3
63 goto 105 (+42)
66 aload_2
67 ldc #22 <SUMMER>
69 invokevirtual #21 <java/lang/String.equals>
72 ifeq 105 (+33)
75 iconst_1
76 istore_3
77 goto 105 (+28)
80 aload_2
81 ldc #23 <AUTUMN>
83 invokevirtual #21 <java/lang/String.equals>
86 ifeq 105 (+19)
89 iconst_2
90 istore_3
91 goto 105 (+14)
94 aload_2
95 ldc #24 <WINTER>
97 invokevirtual #21 <java/lang/String.equals>
100 ifeq 105 (+5)
103 iconst_3
104 istore_3
105 iload_3
106 tableswitch 0 to 3 0: 136 (+30)
1: 139 (+33)
2: 142 (+36)
3: 145 (+39)
default: 145 (+39)
136 goto 145 (+9)
139 goto 145 (+6)
142 goto 145 (+3)
145 return
四、无条件跳转
4.1、无条件跳转指令
目前主要的无条件跳转指令为goto。指令goto接收两个字节的操作数,共同组成一个带符号的整数,用于指定指令的偏移量,指令执行的目的就是跳转到偏移量给定的位置处。
如果指令偏移量太大,超过双字节的带符号整数的范围,则可以使用指令goto_w,它和goto有相同的作用,但是它接收4个字节的操作数,可以表示更大的地址范围。
指令jsr、jsr_w、 ret虽然也是无条件跳转的,但主要用于try-finally语句,且已经被虚拟机逐渐废弃,故不在这里介绍这两个指令。
4.2、举例
用int类型进行加加或者减减都是iinc 1 by 1,double类型是通过加载一个1(dconst_1)在和局部变量中dload_1的值进行相加完成的,所以循环中用int进行加加和减减效果会好些。
while和for在字节码层面都是一样的,唯一不同的是i的作用域有差别。