条件跳转的例子,绝对值
public class abs { public static int abs(int a) { if (a<0) return -a; return a; } }
编译
javac abs.java
反编译
javap -c -verbose abs.class
public static int abs(int); descriptor: (I)I flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=1, args_size=1 0: iload_0 1: ifge 7 4: iload_0 5: ineg 6: ireturn 7: iload_0 8: ireturn
其中ifge 7 意思是,当栈顶的值大于等于0的时候跳转到偏移位7,任何的ifXX指令都会将栈中的值弹出用于进行比较
其中ineg 意思是将栈顶int型数值取负并将结果压入栈顶
再看一个例子,取最小值
public class min { public static int min (int a, int b) { if (a>b) return b; return a; } }
反编译
public static int min(int, int); descriptor: (II)I flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=2, args_size=2 0: iload_0 1: iload_1 2: if_icmple 7 5: iload_1 6: ireturn 7: iload_0 8: ireturn
if_icmple会从栈中弹出两个值进行比较,如果第二个(a,iload_0)小于或者等于第一个(b,iload_1),那么跳转到偏移位7
从上面例子可以看出在Java代码中if条件中的测试与在字节码中是完全相反的
max函数例子
public class max { public static int max (int a, int b) { if (a>b) return a; return b; } }
反编译
public static int max(int, int); descriptor: (II)I flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=2, args_size=2 0: iload_0 1: iload_1 2: if_icmple 7 5: iload_0 6: ireturn 7: iload_1 8: ireturn
代码和min的差不多,唯一的区别是最后两个iload指令(偏移位5和偏移位7)互换了
更复杂的例子
public class cond { public static void f(int i) { if (i<100) System.out.print("<100"); if (i==100) System.out.print("==100"); if (i>100) System.out.print(">100"); if (i==0) System.out.print("==0"); } }
反编译
public static void f(int); descriptor: (I)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: iload_0 1: bipush 100 3: if_icmpge 14 6: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 9: ldc #3 // String <100 11: invokevirtual #4 // Method java/io/PrintStream.print:(Ljava/lang/String;)V 14: iload_0 15: bipush 100 17: if_icmpne 28 20: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 23: ldc #5 // String ==100 25: invokevirtual #4 // Method java/io/PrintStream.print:(Ljava/lang/String;)V 28: iload_0 29: bipush 100 31: if_icmple 42 34: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 37: ldc #6 // String >100 39: invokevirtual #4 // Method java/io/PrintStream.print:(Ljava/lang/String;)V 42: iload_0 43: ifne 54 46: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 49: ldc #7 // String ==0 51: invokevirtual #4 // Method java/io/PrintStream.print:(Ljava/lang/String;)V 54: return
if_icmpge 14 栈顶弹出两个值,并且比较两个数值,如果第的二个值大于或等于第一个,跳转到偏移位14
if_icmpne 28 栈顶弹出两个值,并且比较两个数值,如果第的二个值不等于第一个,跳转到偏移位28
ifne 54 当栈顶int型数值不等于0时跳转到偏移位54
传参例子
public class minmax { public static int min (int a, int b) { if (a>b) return b; return a; } public static int max (int a, int b) { if (a>b) return a; return b; } public static void main(String[] args) { int a=123, b=456; int max_value=max(a, b); int min_value=min(a, b); System.out.println(min_value); System.out.println(max_value); } }
反编译
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=5, args_size=1 0: bipush 123 2: istore_1 3: sipush 456 6: istore_2 7: iload_1 8: iload_2 9: invokestatic #2 // Method max:(II)I 12: istore_3 13: iload_1 14: iload_2 15: invokestatic #3 // Method min:(II)I 18: istore 4 20: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream; 23: iload 4 25: invokevirtual #5 // Method java/io/PrintStream.println:(I)V 28: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream; 31: iload_3 32: invokevirtual #5 // Method java/io/PrintStream.println:(I)V 35: return
istore_1 将栈顶元素弹出并存到本地变量数组1号元素,因为0号给了this
位操作
public class bitop { public static int set (int a, int b) { return a | 1<<b; } public static int clear (int a, int b) { return a & (~(1<<b)); } }
反编译
public static int set(int, int); descriptor: (II)I flags: ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=2, args_size=2 0: iload_0 1: iconst_1 2: iload_1 3: ishl 4: ior 5: ireturn LineNumberTable: line 5: 0 public static int clear(int, int); descriptor: (II)I flags: ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=2, args_size=2 0: iload_0 1: iconst_1 2: iload_1 3: ishl 4: iconst_m1 5: ixor 6: iand 7: ireturn LineNumberTable: line 10: 0
set函数的指令解释
0: iload_0 //载入第0个参数即a,压入栈
1: iconst_1 //数字1压入栈
2: iload_1 ///载入第1个参数即b,压入栈
3: ishl //弹出栈顶两个元素,将int型数值左移位指定位数并将结果压入栈顶,第一个弹出值(b)为左移的位数,第二个弹出的值(1)为要对其进行左移的数,即对1左移b位
4: ior //将栈顶两int型数值作“按位或”并将结果压入栈顶
5: ireturn //返回栈顶值
clear函数中iconst_m1指令将-1压入栈,-1就是16进制的0xFFFFFFFF, ixor是进行异或操作,操作结果相当于取反
将上面的例子扩展成long类型
public class longbitop { public static long lset (long a, int b) { return a | 1<<b; } public static long lclear (long a, int b) { return a & (~(1<<b)); } }
反编译
public static long lset(long, int); descriptor: (JI)J flags: ACC_PUBLIC, ACC_STATIC Code: stack=4, locals=3, args_size=2 0: lload_0 1: iconst_1 2: iload_2 3: ishl 4: i2l 5: lor 6: lreturn LineNumberTable: line 5: 0 public static long lclear(long, int); descriptor: (JI)J flags: ACC_PUBLIC, ACC_STATIC Code: stack=4, locals=3, args_size=2 0: lload_0 1: iconst_1 2: iload_2 3: ishl 4: iconst_m1 5: ixor 6: i2l 7: land 8: lreturn LineNumberTable: line 9: 0
需要注意的指令
i2l 将栈顶int型数值强制转换成long型数值并将结果压入栈顶
32位需要升级为64位值时,会使用i21指令把整型扩展成64位长整型.
循环
看个简单的例子
public class Loop { public static void main(String[] args) { for (int i = 1; i <= 10; i++) { System.out.println(i); } } }
反编译
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=2, args_size=1 0: iconst_1 1: istore_1 2: iload_1 3: bipush 10 5: if_icmpgt 21 8: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 11: iload_1 12: invokevirtual #3 // Method java/io/PrintStream.println:(I)V 15: iinc 1, 1 18: goto 2 21: return
指令解释
0: iconst_1 //常数1压入栈
1: istore_1 //栈顶弹出存入本地变量数组1号元素
2: iload_1 //本地变量1号元素压入栈顶
3: bipush 10 //常数10压入栈顶
5: if_icmpgt 21 // 比较栈顶两int型数值大小,当结果大于0时跳转到偏移位21,比较都是拿第二个弹出值与第一个比较即i与10比较
8: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
11: iload_1
12: invokevirtual #3 // Method java/io/PrintStream.println:(I)V //8~12这里输出变量i
15: iinc 1, 1 //将指定int型变量增加指定值(常用于i++,i--,i+=2) ,这里指本地变量1号元素加1
18: goto 2 //跳转到偏移位2
21: return
多说一句,我们调用println打印数据类型是整型,我们看注释,“(I)V”,I的意思是整型,V的意思是返回void
再看个复杂的,斐波那契数列
public class Fibonacci { public static void main(String[] args) { int limit = 20, f = 0, g = 1; for (int i = 1; i <= limit; i++) { f = f + g; g = f - g; System.out.println(f); } } }
反编译
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=5, args_size=1 0: bipush 20 2: istore_1 3: iconst_0 4: istore_2 5: iconst_1 6: istore_3 7: iconst_1 8: istore 4 10: iload 4 12: iload_1 13: if_icmpgt 37 16: iload_2 17: iload_3 18: iadd 19: istore_2 20: iload_2 21: iload_3 22: isub 23: istore_3 24: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 27: iload_2 28: invokevirtual #3 // Method java/io/PrintStream.println:(I)V 31: iinc 4, 1 34: goto 10 37: return
拿几个指令说一下
if_icmpgt 37 比较栈顶两int型数值大小,当结果大于0时跳转到偏移位37,比较都是拿栈的第二个弹出值(本地变量4号)与第一个比较(本地变量1号)
isub 栈顶的两个元素相减并将结果压入栈顶,谁减谁,仍然是栈顶弹出的第二个减第一个
iinc 4, 1 表示是本地变量4号元素加1,结果存在本地变量4号
这里的英文作者又吐槽了一下
8: istore 4
10: iload 4
这两句中的iload 4,感觉多余。。
switch例子
public class tableswitch { public static void f(int a) { switch (a) { case 0: System.out.println("zero"); break; case 1: System.out.println("one\n"); break; case 2: System.out.println("two\n"); break; case 3: System.out.println("three\n"); break; case 4: System.out.println("four\n"); break; default: System.out.println("something unknown\n"); break; } } }
反编译
public static void f(int); descriptor: (I)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: iload_0 1: tableswitch { // 0 to 4 0: 36 1: 47 2: 58 3: 69 4: 80 default: 91 } 36: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 39: ldc #3 // String zero 41: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 44: goto 99 47: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 50: ldc #5 // String one\n 52: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 55: goto 99 58: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 61: ldc #6 // String two\n 63: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 66: goto 99 69: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 72: ldc #7 // String three\n 74: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 77: goto 99 80: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 83: ldc #8 // String four\n 85: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 88: goto 99 91: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 94: ldc #9 // String something unknown\n 96: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 99: return
其中需要注意的是tableswitch是在JVM级别上直接支持switch 语句,非常有意思的是,编译器编译的时候,会根据case条件的不同翻译成tableswitch或者lookupswitch
1: tableswitch { // 0 to 4
0: 36
1: 47
2: 58
3: 69
4: 80
default: 91
}
的意思,将栈顶元素与大括号内冒号前的元素逐个比较,若相等,则跳转到右边对应的偏移块号
break对应其中的goto 99,如果没有这句,栈顶元素会继续和后面的大括号内后续元素进行比较
原文地址:http://blog.51cto.com/7317859/2105656