java中字节码_Java字节码浅析(三)
英文原文鏈接,譯文鏈接,原文作者:James Bloom,譯者:有孚
從Java7開始,switch語句增加了對String類型的支持。不過字節碼中的switch指令還是只支持int類型,并沒有增加對其它類型的支持。事實上switch語句對String的支持是分成兩個步驟來完成的。首先,將每個case語句里的值的hashCode和操作數棧頂的值(譯注:也就是switch里面的那個值,這個值會先壓入棧頂)進行比較。這個可以通過lookupswitch或者是tableswitch指令來完成。結果會路由到某個分支上,然后調用String.equlals來判斷是否確實匹配。最后根據equals返回的結果,再用一個tableswitch指令來路由到具體的case分支上去執行。
public int simpleSwitch(String stringOne) {
switch (stringOne) {
case "a":
return 0;
case "b":
return 2;
case "c":
return 3;
default:
return 4;
}
}
這個字符串的switch語句會生成下面的字節碼:
0: aload_1
1: astore_2
2: iconst_m1
3: istore_3
4: aload_2
5: invokevirtual #2 // Method java/lang/String.hashCode:()I
8: tableswitch {
default: 75
min: 97
max: 99
97: 36
98: 50
99: 64
}
36: aload_2
37: ldc #3 // String a
39: invokevirtual #4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
42: ifeq 75
45: iconst_0
46: istore_3
47: goto 75
50: aload_2
51: ldc #5 // String b
53: invokevirtual #4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
56: ifeq 75
59: iconst_1
60: istore_3
61: goto 75
64: aload_2
65: ldc #6 // String c
67: invokevirtual #4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
70: ifeq 75
73: iconst_2
74: istore_3
75: iload_3
76: tableswitch {
default: 110
min: 0
max: 2
0: 104
1: 106
2: 108
}
104: iconst_0
105: ireturn
106: iconst_2
107: ireturn
108: iconst_3
109: ireturn
110: iconst_4
111: ireturn
這段字節碼所在的class文件里面,會包含如下的一個常量池。關于常量池可以看下JVM內部細節中的_運行時常量池_一節。
Constant pool:
#2 = Methodref #25.#26 // java/lang/String.hashCode:()I
#3 = String #27 // a
#4 = Methodref #25.#28 // java/lang/String.equals:(Ljava/lang/Object;)Z
#5 = String #29 // b
#6 = String #30 // c
#25 = Class #33 // java/lang/String
#26 = NameAndType #34:#35 // hashCode:()I
#27 = Utf8 a
#28 = NameAndType #36:#37 // equals:(Ljava/lang/Object;)Z
#29 = Utf8 b
#30 = Utf8 c
#33 = Utf8 java/lang/String
#34 = Utf8 hashCode
#35 = Utf8 ()I
#36 = Utf8 equals
#37 = Utf8 (Ljava/lang/Object;)Z
注意,在執行這個switch語句的時候,用到了兩個tableswitch指令,同時還有數個invokevirtual指令,這個是用來調用String.equals()方法的。在下一篇文章中關于方法調用的那節,會詳細介紹到這個invokevirtual指令。下圖演示了輸入為”b”的情況下,這個swith語句是如何執行的。
如果有幾個分支的hashcode是一樣的話,比如說“FB”和”Ea”,它們的hashCode都是28,得簡單的調整下equals方法的處理流程來進行處理。在下面的這個例子中,34行處的字節碼ifeg 42會跳轉到另一個String.equals方法調用,而不是像前面那樣執行lookupswitch指令,因為前面的那個例子中hashCode沒有沖突。(譯注:這里一般容易弄混淆,認為ifeq是字符串相等,為什么要跳到下一處繼續比較字符串?其實ifeq是判斷棧頂元素是否和0相等,而棧頂的值就是String.equals的返回值,而true,也就是相等,返回的是1,false返回的是0,因此ifeq為真的時候表明返回的是false,這會兒就應該繼續進行下一個字符串的比較)
public int simpleSwitch(String stringOne) {
switch (stringOne) {
case "FB":
return 0;
case "Ea":
return 2;
default:
return 4;
}
}
這段代碼會生成下面的字節碼:
0: aload_1
1: astore_2
2: iconst_m1
3: istore_3
4: aload_2
5: invokevirtual #2 // Method java/lang/String.hashCode:()I
8: lookupswitch {
default: 53
count: 1
2236: 28
}
28: aload_2
29: ldc #3 // String Ea
31: invokevirtual #4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
34: ifeq 42
37: iconst_1
38: istore_3
39: goto 53
42: aload_2
43: ldc #5 // String FB
45: invokevirtual #4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
48: ifeq 53
51: iconst_0
52: istore_3
53: iload_3
54: lookupswitch {
default: 84
count: 2
0: 80
1: 82
}
80: iconst_0
81: ireturn
82: iconst_2
83: ireturn
84: iconst_4
85: ireturn
###循環語句
if-else和switch這些條件流程控制語句都是先通過一條指令比較兩個值,然后跳轉到某個分支去執行。
for循環和while循環這些語句也類似,只不過它們通常都包含一個goto指令,使得字節碼能夠循環執行。do-while循環則不需要goto指令,因為它們的條件判斷指令是放在循環體的最后來執行。
有一些操作碼能在單條指令內完成整數或者引用的比較,然后根據結果跳轉到某個分支繼續執行。而比較double,long,float這些類型則需要兩條指令。首先會將兩個值進行比較,然后根據結果把1,-1,0壓入操作數棧中。然后再根據棧頂的值是大于小于或者等于0,來決定下一步要執行的指令的位置。這些指令在上一篇文章中有詳細的介紹。
####while循環
while循環包含條件跳轉指令比如if_icmpge 或者if_icmplt(前面有介紹)以及goto指令。如果判斷條件不滿足的話,會跳轉到循環體后的第一條指令繼續執行,循環結束(譯注:這里判斷條件和代碼中的正好相反,如代碼中是i<2,字節碼內是i>=2,從字節碼的角度看,是滿足條件后循環中止)。循環體的末尾是一條goto指令,它會跳轉到循環開始的地方繼續執行,直到分支跳轉的條件滿足才終止。
public void whileLoop() {
int i = 0;
while (i < 2) {
i++;
}
}
編譯完后是:
0: iconst_0
1: istore_1
2: iload_1
3: iconst_2
4: if_icmpge 13
7: iinc 1, 1
10: goto 2
13: return
if_icmpge指令會判斷局部變量區中的1號位的變量(也就是i,譯注:局部變量區從0開始計數,第0位是this)是否大于等于2,如果不是繼續執行,如果是的話跳轉到13行處,結束循環。goto指令使得循環可以繼續執行,直到條件判斷為真,這個時候會跳轉到緊挨著循環體后邊的return指令處。iinc是少數的幾條能直接更新局部變量區里的變量的指令之一,它不用把值壓到操作數棧里面就能直接進行操作。這里iinc指令把第1個局部變量(譯注:第0個是this)自增1。
for循環和while循環在字節碼里的格式是一樣的。這并不奇怪,因為每個while循環都可以很容易改寫成一個for循環。比如上面的while循環就可以改寫成下面的for循環,當然了它們輸出的字節碼也是一樣的:
public void forLoop() {
for(int i = 0; i < 2; i++) {
}
}
####do-while循環
do-while循環和for循環,while循環非常類似,除了一點,它是不需要goto指令的,因為條件跳轉指令在循環體的末尾,可以用它來跳轉回循環體的起始處。
public void doWhileLoop() {
int i = 0;
do {
i++;
} while (i < 2);
}
這會生成如下的字節碼:
0: iconst_0
1: istore_1
2: iinc 1, 1
5: iload_1
6: iconst_2
7: if_icmplt 2
10: return
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的java中字节码_Java字节码浅析(三)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: pacman安装ubuntu_Ubunt
- 下一篇: java websocket 库_Jav