while程序的字节码分析
首先来看两个最简单的while程序,不包含任何变量:
- 程序1:
public class Test {
public static void main(String[] args) {
while(true) {
}
}
}
字节码指令:
public class com.wangyao2221.jvm.test.Test {
public com.wangyao2221.jvm.test.Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: goto 0
}
可以看到字节码的main方法中只有一个0: goto 0,没有选择分支,说明Java虚拟机节过程中已经知道这是一个特殊的循环,也就是死循环,这条字节码指令表示的就是自身跳转到自身。
- 程序2:
public class Test {
public static void main(String[] args) {
while(false) {
}
}
}
该程序成功编译,原因是while的语句块不可达,报错内容如下:
.\Test.java:5: 错误: 无法访问的语句
while(false) {
^
1 个错误
while(false) {}无法成功编译,于是想看看if(false) {}能否成功编译,如程序3所示:
- 程序3:
public class Test {
public static void main(String[] args) {
if(false) {
}
// 不妨也试试if(true) {}
if(true) {
}
}
}
可以成功编译且字节码如下:
public class com.wangyao2221.jvm.test.Test {
public com.wangyao2221.jvm.test.Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: return
}
可以看到字节码指令的main方法直接就是一个return,说明Java虚拟机解析过程中已经知道两个if语句没有任何动作需要执行,所以字节码直接就丢弃了这几行多余的代码。为了验证这个想法,此时我们往两个if语句中加入一条简单的语句,如程序4
- 程序4:
public class Test {
public static void main(String[] args) {
if(false) {
int a = 0;
}
if(true) {
int b = 0;
}
}
}
字节码指令如下:
public class com.wangyao2221.jvm.test.Test {
public com.wangyao2221.jvm.test.Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_0 // 将常数0压入操作数栈中
1: istore_1 // 弹出操作数栈栈顶元素,存到局部变量表第1个位置
2: return
}
可以看到字节码指令中比程序3多出了0: iconst_0、 1: istore_1,说明if(false){}还是直接被丢弃了,而if(true){int b = 0;}则被解析成多出来的两行字节码指令——定义变量b并初始化为0
补充几条指令的区别(iconst、bipush、iconst_m1、sipush、ldc):
取值-1~5采用iconst指令
取值-128~127采用bipush指令
取值-1时,虚拟机使用iconst_m1指令
取值-32768~32767采用sipush指令
取值-2147483648~2147483647采用 ldc 指令
上面程序3、程序4都是有点跑题,主要是由while引出的两个疑问,下面回到while程序上,让while程序复杂一点来观察字节码,首先看程序5
- 程序5:
public class Test {
public static void main(String[] args) {
int n = 5;
while (n-- > 0) {
int a = 0;
}
}
}
这段代码中增加了一个变量,作为循环的条件,在循环过程中自减,字节码指令如下:
public class com.wangyao2221.jvm.test.Test {
public com.wangyao2221.jvm.test.Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_5 // 将常量5压入操作数栈中
1: istore_1 // 弹出操作数栈栈顶元素,存到局部变量表第1个位置
2: iload_1 // while在此处开始,取出局部变量表第1个位置的值(n),压入操作数栈
3: iinc 1, -1 // 对局部变量表的第1个位置的值进行减1操作
6: ifle 14 // 将操作数栈顶的的元素(n)与0作比较(似乎没有出现0?看下面解释),如果小于等于0则跳转到底14行return处,如果这行不成立则按顺序到接下来的第9行
9: iconst_0 // 指令9 10是while花括号内的代码,此行是将常量0压入操作数栈中
10: istore_2 // 弹出操作数栈栈顶元素,存到局部变量表第2个位置
11: goto 2 // 跳转到2,继续下一次循环
14: return
}
可以看到,如果多加了一个循环遍历,字节码指令就多了很多行,大部分解释已经在字节码中进行了注释,需要补充的是ifle这条指令,if顾名思义就是判断的意思,而le表示的less equal(小于等于),但是此处并没有出现0,为什么是和0比较呢?因为ifle在字节码指令中就是表示和0比较,和其他数值比较有其他的指令,严格来说ifle属于跳转指令,即如果条件成立则跳转到指定行,关于JVM其他if跳转指令如下所示:
指令 | . | ||
---|---|---|---|
IFEQ | … , i | … | jump if i == 0 |
IFNE | … , i | … | jump if i != 0 |
IFLT | … , i | … | jump if i < 0 |
IFGE | … , i | … | jump if i >= 0 |
IFGT | … , i | … | jump if i > 0 |
IFLE | … , i | … | jump if i <= 0 |
IF_ICMPEQ | … , i , j | … | jump if i == j |
IF_ICMPNE | … , i , j | … | jump if i != j |
IF_ICMPLT | … , i , j | … | jump if i < j |
IF_ICMPGE | … , i , j | … | jump if i >= j |
IF_ICMPGT | … , i , j | … | jump if i > j |
IF_ICMPLE | … , i , j | … | jump if i <= j |
IF_ACMPEQ | … , o , p | … | jump if o == p |
IF_ACMPNE | … , o , p | … | jump if o != p |
IFNULL | … , o | … | jump if o == null |
IFNONNULL | … , o | … | jump if o != null |
程序5中循环条件是n-- > 0
,那如果是--n > 0
字节码会是怎样的呢?我们知道n–是先取值再自减,–n则是先自减再取值,所以字节码必定是有所不同的,继续看程序6及其字节码
- 程序6:
public class Test {
public static void main(String[] args) {
int n = 5;
while (--n > 0) { //n--改为--n
int a = 0;
}
}
}
对应字节码指令如下:
public class com.wangyao2221.jvm.test.Test {
public com.wangyao2221.jvm.test.Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_5
1: istore_1
2: iinc 1, -1 // 对局部变量表的第1个位置的值进行减1操作
5: iload_1 // while在此处开始,取出局部变量表第1个位置的值(n),压入操作数栈
6: ifle 14
9: iconst_0
10: istore_2
11: goto 2
14: return
}
字节码指令除iinc和iload_1外其他部分与程序5是一样的,此处只给出这两行字节码指令的注释,可以发现这两行与程序5的顺序反了一下,这就是n–和--n在字节码层面的原理,这里稍作解释:①iinc 1,-1是对局部变量表第一个位置的值进行减1,②iload_1是取的是局部变量表第1个位置的值到操作数栈中,③然后ifle是从操作数栈中取出栈顶元素来使用,①是自减操作,②③就是一个取值操作,所以①和②交换顺序就体现出了n–和--n的区别
以上是本人对while循环字节码做的一些实验和分析,如果有什么问题希望帮忙指证