程序流程离不开条件控制,为了支持条件跳转,虚拟机提供了大量字节码指令,大体上可以分为
1)比较指令、
2)条件跳转指令、
3)比较条件跳转指令、
4)多条件分支跳转指令、
5)无条件跳转指令等。

一、条件跳转指令

1.1、说明

条件跳转指令通常和比较指令结合使用。在条件跳转指令执行前,一般可以先用比较指令进行栈顶元素的准备,然后进行条件跳转。
条件跳转指令有: ifeq, iflt, ifle, ifne, ifgt,ifge,ifnull,ifnonnull。这些指令都接收两个字节的操作数,用于计算跳转的位置(16位符号整数作为当前位置的offset)。
它们的统一含义为:弹出栈顶元素,测试它是否满足某一条件,如果满足条件,则跳转到给定位置。

具体说明:
image.png
注意:

  1. 与前面的运算规则一致:
    1. 对于boolean、byte、char、short类型的条件分支比较操作,都是使用int类型的比较指令完成
    2. 对于long、float、double类型的条件分支比较操作,则会先执行相应类型的比较运算指令,运算指令会返回一个整型值到操作数栈中,随后再执行int类型的条件分支比较操作来完成整个分支跳转
  2. 由于各类型的比较最终都会转为 int类型的比较操作,所以Java虚拟机提供的int类型的条件分支指令是最为丰富和强大的。

    1.2、举例

    1.2.1 例子一:数字比较
    ifne 12(+9)是指是否等于0,如果等于就跳转到操作码12的位置,如果不等于就继续向下执行,goto 15(+6)是指跳转到操作码15的位置
    image.png
    跟不等于进行对比
    image.png
    1.2.2 例子二:字符串比较
    image.png
    1.2.3 例子三:结合比较指令
    fcompg比较9.0<10.0,如果大于就压入一个-1,ifge是比较-1>=0,条件不满足,不用跳转,指令继续往下走,iconst_1往操作数栈中压入一个1,在调取invokevirual #5 PrintStream.println方法的时候它会将1转换成true后再进行输出。image.png
    image.png
    跟小于做比较
    image.png
    image.png
    两个int的数值比较在比较条件跳转指令中

    二、比较条件跳转指令

    2.1、说明

    比较条件跳转指令类似于比较指令和条件跳转指令的结合体,它将比较和跳转两个步骤合二为一。

这类指令有:ificmpeq、if_icmpne、if_icmplt、if_icmpgt、 if_icmple、if_icmpge、if_acmpeq和if_acmpne。其中指令助记符加上“if”后,以字符“i”开头的指令针对int型整数操作(也包括short和byte类型),以字符“a”开头的指令表示对象引用的比较。

具体说明:
image.png
这些指令都接收两个字节的操作数作为参数,用于计算跳转的位置。同时在执行指令时,栈顶需要准备两个元素进行比较。指令执行完成后,栈顶的这两个元素被清空,且没有任何数据入栈。如果预设条件成立,则执行跳转,否则,继续执行下一条语句。

2.2、举例

image.png
image.png
image.png

三、多条件分支跳转

3.1、多条件分支跳转指令

多条件分支跳转指令是专为switch-case语句设计的,主要有tableswitch和lookupswitch。
image.png
从助记符上看,两者都是switch语句的实现,它们的区别:

  • tableswitch要求多个条件分支值是连续的,它内部只存放起始值和终止值,以及若干个跳转偏移量,通过给定的操作数index,可以立即定位到跳转偏移量位置,因此效率比较高。
  • 指令lookupswitch内部存放着各个离散的case-offset对,每次执行都要搜索全部的case-offset对,找到匹配的case值,并根据对应的offset计算跳转地址,因此效率较低。

指令tableswitch的示意图如下图所示。由于tableswitch的case值是连续的,因此只需要记录最低值和最高值,以及每一项对应的offset偏移量,根据给定的index值通过简单的计算即可直接定位到offset。
image.png
指令lookupswitch处理的是离散的case值,但是出于效率考虑,将case-offset对按照case值大小排序,给定index时,需要查找与index相等的case,获得其offset,如果找不到则跳转到default。指令lookupswitch 如下图所示
image.png

3.2、举例

连续的case
image.png
如果代码case里没有break那字节码中就没有goto(无条件跳转指令)
image.png
不连续的case
image.png
代码:
image.png
字节码:

  1. 0 aload_1
  2. 1 astore_2
  3. 2 iconst_m1
  4. 3 istore_3
  5. 4 aload_2
  6. 5 invokevirtual #19 <java/lang/String.hashCode>
  7. 8 lookupswitch 4
  8. -1842350579: 52 (+44)
  9. -1837878353: 66 (+58)
  10. -1734407483: 94 (+86)
  11. 1941980694: 80 (+72)
  12. default: 105 (+97)
  13. 52 aload_2
  14. 53 ldc #20 <SPRING>
  15. 55 invokevirtual #21 <java/lang/String.equals>
  16. 58 ifeq 105 (+47)
  17. 61 iconst_0
  18. 62 istore_3
  19. 63 goto 105 (+42)
  20. 66 aload_2
  21. 67 ldc #22 <SUMMER>
  22. 69 invokevirtual #21 <java/lang/String.equals>
  23. 72 ifeq 105 (+33)
  24. 75 iconst_1
  25. 76 istore_3
  26. 77 goto 105 (+28)
  27. 80 aload_2
  28. 81 ldc #23 <AUTUMN>
  29. 83 invokevirtual #21 <java/lang/String.equals>
  30. 86 ifeq 105 (+19)
  31. 89 iconst_2
  32. 90 istore_3
  33. 91 goto 105 (+14)
  34. 94 aload_2
  35. 95 ldc #24 <WINTER>
  36. 97 invokevirtual #21 <java/lang/String.equals>
  37. 100 ifeq 105 (+5)
  38. 103 iconst_3
  39. 104 istore_3
  40. 105 iload_3
  41. 106 tableswitch 0 to 3 0: 136 (+30)
  42. 1: 139 (+33)
  43. 2: 142 (+36)
  44. 3: 145 (+39)
  45. default: 145 (+39)
  46. 136 goto 145 (+9)
  47. 139 goto 145 (+6)
  48. 142 goto 145 (+3)
  49. 145 return

四、无条件跳转

4.1、无条件跳转指令

目前主要的无条件跳转指令为goto。指令goto接收两个字节的操作数,共同组成一个带符号的整数,用于指定指令的偏移量,指令执行的目的就是跳转到偏移量给定的位置处。
如果指令偏移量太大,超过双字节的带符号整数的范围,则可以使用指令goto_w,它和goto有相同的作用,但是它接收4个字节的操作数,可以表示更大的地址范围。
指令jsr、jsr_w、 ret虽然也是无条件跳转的,但主要用于try-finally语句,且已经被虚拟机逐渐废弃,故不在这里介绍这两个指令。
image.png

4.2、举例

image.png
用int类型进行加加或者减减都是iinc 1 by 1,double类型是通过加载一个1(dconst_1)在和局部变量中dload_1的值进行相加完成的,所以循环中用int进行加加和减减效果会好些。
image.png
image.png
while和for在字节码层面都是一样的,唯一不同的是i的作用域有差别。
image.png
image.png