Java byte code 的學(xué)習(xí)意義

Java byte code 的學(xué)習(xí)意義

為啥要學(xué)java bytecode,這就跟你問我已經(jīng)會(huì)python了為啥要學(xué)匯編一個(gè)道理。
我為啥要整理這些難記的知識(shí) ? 因?yàn)楣居玫搅四碁榈囊粋€(gè)據(jù)說極其牛逼的技術(shù)框架,但是某為不給源碼,導(dǎo)致理解總是膚淺的。

要了解應(yīng)用,唯有剝開拆散了研究一番,于是JD就上了,但是還是有些代碼反編譯不出來,這時(shí)候 javap 就有用了,從指令級(jí)回推代碼。

先轉(zhuǎn)一份IBM牛人的筆記: http://it.deepinmind.com

Java字節(jié)碼淺析(—)
英文原文鏈接,譯文鏈接,原文作者:James Bloom,譯者:有孚
明白Java代碼是如何編譯成字節(jié)碼并在JVM上運(yùn)行的非常重要,這有助于理解程序運(yùn)行的時(shí)候究竟發(fā)生了些什么。理解這點(diǎn)不僅能搞清語言特性是如何實(shí)現(xiàn)的,并且在做方案討論的時(shí)候能清楚相應(yīng)的副作用及權(quán)衡利弊。
本文介紹了Java代碼是如何編譯成字節(jié)碼并在JVM上執(zhí)行的。想了解JVM的內(nèi)部結(jié)構(gòu)以及字節(jié)碼運(yùn)行時(shí)用到的各個(gè)內(nèi)存區(qū)域,可以看下我前面的一篇關(guān)于JVM內(nèi)部細(xì)節(jié)的文章。
本文分為三部分,每一部分都分成幾個(gè)小節(jié)。每個(gè)小節(jié)都可以單獨(dú)閱讀,不過由于一些概念是逐步建立起來的,如果你依次閱讀完所有章節(jié)會(huì)更簡(jiǎn)單一些。每一節(jié)都會(huì)覆蓋到Java代碼中的不同結(jié)構(gòu),并詳細(xì)介紹了它們是如何編譯并執(zhí)行的。

  1. 第一部分, 基礎(chǔ)概念
    變量
    局部變量
    JVM是一個(gè)基于棧的架構(gòu)。方法執(zhí)行的時(shí)候(包括main方法),在棧上會(huì)分配一個(gè)新的幀,這個(gè)棧幀包含一組局部變量。這組局部變量包含了方法運(yùn)行過程中用到的所有變量,包括this引用,所有的方法參數(shù),以及其它局部定義的變量。對(duì)于類方法(也就是static方法)來說,方法參數(shù)是從第0個(gè)位置開始的,而對(duì)于實(shí)例方法來說,第0個(gè)位置上的變量是this指針。
    局部變量可以是以下這些類型:
  • char
  • long
  • short
  • int
  • float
  • double
  • 引用
  • 返回地址
    除了long和double類型外,每個(gè)變量都只占局部變量區(qū)中的一個(gè)變量槽(slot),而long及double會(huì)占用兩個(gè)連續(xù)的變量槽,因?yàn)檫@些類型是64位的。
    當(dāng)一個(gè)新的變量創(chuàng)建的時(shí)候,操作數(shù)棧(operand stack)會(huì)用來存儲(chǔ)這個(gè)新變量的值。然后這個(gè)變量會(huì)存儲(chǔ)到局部變量區(qū)中對(duì)應(yīng)的位置上。如果這個(gè)變量不是基礎(chǔ)類型的話,本地變量槽上存的就只是一個(gè)引用。這個(gè)引用指向堆的里一個(gè)對(duì)象。
    比如:
    int i = 5;

編譯后就成了
0: bipush 5
2: istore_0

bipush

用來將一個(gè)字節(jié)作為整型數(shù)字壓入操作數(shù)棧中,在這里5就會(huì)被壓入操作數(shù)棧上。

istore_0

這是istore_這組指令集(譯注:嚴(yán)格來說,這個(gè)應(yīng)該叫做操作碼,opcode ,指令是指操作碼加上對(duì)應(yīng)的操作數(shù),oprand。不過操作碼一般作為指令的助記符,這里統(tǒng)稱為指令)中的一條,這組指令是將一個(gè)整型數(shù)字存儲(chǔ)到本地變量中。n代表的是局部變量區(qū)中的位置,并且只能是0,1,2,3。再多的話只能用另一條指令istore了,這條指令會(huì)接受一個(gè)操作數(shù),對(duì)應(yīng)的是局部變量區(qū)中的位置信息。

這條指令執(zhí)行的時(shí)候,內(nèi)存布局是這樣的:



class文件中的每一個(gè)方法都會(huì)包含一個(gè)局部變量表,如果這段代碼在一個(gè)方法里面的話,你會(huì)在類文件的局部變量表中發(fā)現(xiàn)如下的一條記錄。
LocalVariableTable:
Start Length Slot Name Signature
0 1 1 i I

字段
Java類里面的字段是作為類對(duì)象實(shí)例的一部分,存儲(chǔ)在堆里面的(類變量對(duì)應(yīng)存儲(chǔ)在類對(duì)象里面)。關(guān)于字段的信息會(huì)添加到類文件里的field_info數(shù)組里,像下面這樣:
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info contant_pool[constant_pool_count – 1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}

另外,如果變量被初始化了,那么初始化的字節(jié)碼會(huì)加到構(gòu)造方法里。
下面這段代碼編譯了之后:
public class SimpleClass {

public int simpleField = 100;

}

如果你用javap進(jìn)行反編譯,這個(gè)被添加到了field_info數(shù)組里的字段會(huì)多出一段描述信息。
public int simpleField;
Signature: I
flags: ACC_PUBLIC

初始化變量的字節(jié)碼會(huì)被加到構(gòu)造方法里,像下面這樣:
public SimpleClass();
Signature: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: bipush 100
7: putfield #2 // Field simpleField:I
10: return

aload_0

從局部變量數(shù)組中加載一個(gè)對(duì)象引用到操作數(shù)棧的棧頂。盡管這段代碼看起來沒有構(gòu)造方法,但是在編譯器生成的默認(rèn)的構(gòu)造方法里,就會(huì)包含這段初始化的代碼。第一個(gè)局部變量正好是this引用,于是aload_0把this引用壓到操作數(shù)棧中。aload_0是aload_指令集中的一條,這組指令會(huì)將引用加載到操作數(shù)棧中。n對(duì)應(yīng)的是局部變量數(shù)組中的位置,并且也只能是0,1,2,3。還有類似的加載指令,它們加載的并不是對(duì)象引用,比如iload_,lload_,fload_,和dload_, 這里i代表int,l代表long,f代表float,d代表double。局部變量的在數(shù)組中的位置大于3的,得通過iload,lload,fload,dload,和aload進(jìn)行加載,這些指令都接受一個(gè)操作數(shù),它代表的是要加載的局部變量的在數(shù)組中的位置。

invokespecial

這條指令可以用來調(diào)用對(duì)象實(shí)例的構(gòu)造方法,私有方法和父類中的方法。它是方法調(diào)用指令集中的一條,其它的還有invokedynamic, invokeinterface, invokespecial, invokestatic, invokevirtual.這里的invokespecial指令調(diào)用的是父類也就是java.lang.Object的構(gòu)造方法。

bipush

它是用來把一個(gè)字節(jié)作為整型壓到操作數(shù)棧中的,在這里100會(huì)被壓到操作數(shù)棧里。

putfield

它接受一個(gè)操作數(shù),這個(gè)操作數(shù)引用的是運(yùn)行時(shí)常量池里的一個(gè)字段,在這里這個(gè)字段是simpleField。賦給這個(gè)字段的值,以及包含這個(gè)字段的對(duì)象引用,在執(zhí)行這條指令的時(shí)候,都 會(huì)從操作數(shù)棧頂上pop出來。前面的aload_0指令已經(jīng)把包含這個(gè)字段的對(duì)象壓到操作數(shù)棧上了,而后面的bipush又把100壓到棧里。最后putfield指令會(huì)將這兩個(gè)值從棧頂彈出。執(zhí)行完的結(jié)果就是這個(gè)對(duì)象的simpleField這個(gè)字段的值更新成了100。

上述代碼執(zhí)行的時(shí)候內(nèi)存里面是這樣的:



這里的putfield指令的操作數(shù)引用的是常量池里的第二個(gè)位置。JVM會(huì)為每個(gè)類型維護(hù)一個(gè)常量池,運(yùn)行時(shí)的數(shù)據(jù)結(jié)構(gòu)有點(diǎn)類似一個(gè)符號(hào)表,盡管它包含的信息更多。Java中的字節(jié)碼操作需要對(duì)應(yīng)的數(shù)據(jù),但通常這些數(shù)據(jù)都太大了,存儲(chǔ)在字節(jié)碼里不適合,它們會(huì)被存儲(chǔ)在常量池里面,而字節(jié)碼包含一個(gè)常量池里的引用 。當(dāng)類文件生成的時(shí)候,其中的一塊就是常量池:
Constant pool:

1 = Methodref #4.#16 // java/lang/Object."<init>":()V

2 = Fieldref #3.#17 // SimpleClass.simpleField:I

3 = Class #13 // SimpleClass

4 = Class #19 // java/lang/Object

5 = Utf8 simpleField

6 = Utf8 I

7 = Utf8 <init>

8 = Utf8 ()V

9 = Utf8 Code

10 = Utf8 LineNumberTable

11 = Utf8 LocalVariableTable

12 = Utf8 this

13 = Utf8 SimpleClass

14 = Utf8 SourceFile

15 = Utf8 SimpleClass.java

16 = NameAndType #7:#8 // "<init>":()V

17 = NameAndType #5:#6 // simpleField:I

18 = Utf8 LSimpleClass;

19 = Utf8 java/lang/Object

常量字段(類常量)
帶有final標(biāo)記的常量字段在class文件里會(huì)被標(biāo)記成ACC_FINAL.
比如
public class SimpleClass {

public final int simpleField = 100;

}

字段的描述信息會(huì)標(biāo)記成ACC_FINAL:
public static final int simpleField = 100;
Signature: I
flags: ACC_PUBLIC, ACC_FINAL
ConstantValue: int 100

對(duì)應(yīng)的初始化代碼并不變:
4: aload_0
5: bipush 100
7: putfield #2 // Field simpleField:I

靜態(tài)變量
帶有static修飾符的靜態(tài)變量則會(huì)被標(biāo)記成ACC_STATIC:
public static int simpleField;
Signature: I
flags: ACC_PUBLIC, ACC_STATIC

不過在實(shí)例的構(gòu)造方法中卻再也找不到對(duì)應(yīng)的初始化代碼了。因?yàn)閟tatic變量會(huì)在類的構(gòu)造方法中進(jìn)行初始化,并且它用的是putstatic指令而不是putfiled。
static {};
Signature: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: bipush 100
2: putstatic #2 // Field simpleField:I
5: return


未完待續(xù)。

條件語句
像if-else, switch這樣的流程控制的條件語句,是通過用一條指令來進(jìn)行兩個(gè)值的比較,然后根據(jù)結(jié)果跳轉(zhuǎn)到另一條字節(jié)碼來實(shí)現(xiàn)的。
循環(huán)語句包括for循環(huán),while循環(huán),它們的實(shí)現(xiàn)方式也很類似,但有一點(diǎn)不同,它們通常都會(huì)包含一條goto指令,以便字節(jié)碼實(shí)現(xiàn)循環(huán)執(zhí)行。do-while循環(huán)不需要goto指令,因?yàn)樗臈l件分支是在字節(jié)碼的末尾。更多細(xì)節(jié)請(qǐng)參考循環(huán)語句一節(jié)。
有一些指令可以用來比較兩個(gè)整型或者兩個(gè)引用,然后執(zhí)行某個(gè)分支,這些操作都能在單條指令里面完成。而像double,float,long這些值需要兩條指令。首先得去比較兩個(gè)值,然后根據(jù)結(jié)果,會(huì)把1,0或者-1壓到棧里。最后根據(jù)棧頂?shù)闹凳谴笥冢扔诨蛘咝∮?來判斷應(yīng)該跳轉(zhuǎn)到哪個(gè)分支。
我們先來介紹下if-else語句,然后再詳細(xì)介紹下分支跳轉(zhuǎn)用到的幾種不同的指令。
if-else
下面的這個(gè)簡(jiǎn)單的例子是用來比較兩個(gè)整數(shù)的:
public int greaterThen(int intOne, int intTwo) {
if (intOne > intTwo) {
return 0;
} else {
return 1;
}
}

方法最后會(huì)編譯成如下的字節(jié)碼:
0: iload_1
1: iload_2
2: if_icmple 7
5: iconst_0
6: ireturn
7: iconst_1
8: ireturn

首先,通過iload_1, iload_2兩條指令將兩個(gè)入?yún)喝氩僮鲾?shù)棧中。if_icmple會(huì)比較棧頂?shù)膬蓚€(gè)值的大小。如果intOne小于或者等于intTwo的話,會(huì)跳轉(zhuǎn)到第7行處的字節(jié)碼來執(zhí)行。可以看到這里和Java代碼里的if語句的條件判斷正好相反,這是因?yàn)樵谧止?jié)碼里面,判斷條件為真的話會(huì)跑到else分支里面去執(zhí)行,而在Java代碼里,判斷為真會(huì)進(jìn)入if塊里面執(zhí)行。換言之,if_icmple判斷的是如果if條件不為真,然后跳過if塊。if代碼塊里對(duì)應(yīng)的代碼是5,6處的字節(jié)碼,而else塊對(duì)應(yīng)的是7,8處的。



下面的代碼則稍微復(fù)雜了一點(diǎn),它需要進(jìn)行兩次比較。
public int greaterThen(float floatOne, float floatTwo) {
int result;
if (floatOne > floatTwo) {
result = 1;
} else {
result = 2;
}
return result;
}

編譯后會(huì)是這樣:
0: fload_1
1: fload_2
2: fcmpl
3: ifle 11
6: iconst_1
7: istore_3
8: goto 13
11: iconst_2
12: istore_3
13: iload_3
14: ireturn

在這個(gè)例子中,首先兩個(gè)參數(shù)會(huì)被fload_1和fload_2指令壓入棧中。和上面那個(gè)例子不同的是,這里需要比較兩回。fcmple先用來比較棧頂?shù)膄loatOne和floatTwo,然后把比較的結(jié)果壓入操作數(shù)棧中。
* floatOne > floatTwo –> 1
* floatOne = floatTwo –> 0
* floatOne < floatTwo –> -1
* floatOne or floatTwo = NaN –> 1

然后通過ifle進(jìn)行判斷,如果前面fcmpl的結(jié)果是< =0的話,則跳轉(zhuǎn)到11行處的字節(jié)碼去繼續(xù)執(zhí)行。
這個(gè)例子還有一個(gè)地方和前面不同的是,它只在方法末有一個(gè)return語句,因此在if代碼塊的最后,會(huì)有一個(gè)goto語句來跳過else塊。goto語句會(huì)跳轉(zhuǎn)到第13條字節(jié)碼處,然后通過iload_3將存儲(chǔ)在局部變量區(qū)第三個(gè)位置的結(jié)果壓入棧中,然后就可以通過return指令將結(jié)果返回了。



除了比較數(shù)值的指令外,還有比較引用是否相等的(==),以及引用是否等于null的(== null或者!=null),以及比較對(duì)象的類型的(instanceof)。
if_icmp<cond>

這組指令用來比較操作數(shù)棧頂?shù)膬蓚€(gè)整數(shù),然后跳轉(zhuǎn)到新的位置去執(zhí)行。<cond>可以是:eq-等于,ne-不等于,lt-小于,le-小于等于,gt-大于, ge-大于等于。

if_acmp<cond>

這兩個(gè)指令用來比較對(duì)象是否相等,然后根據(jù)操作數(shù)指定的位置進(jìn)行跳轉(zhuǎn)。

ifnonnull ifnull

這兩個(gè)指令用來判斷對(duì)象是否為null,然后根據(jù)操作數(shù)指定的位置進(jìn)行跳轉(zhuǎn)。

lcmp

這個(gè)指令用來比較棧頂?shù)膬蓚€(gè)長(zhǎng)整型,然后將結(jié)果值壓入棧中: 如果value1>value2,壓入1,如果value1==value2,壓入0,如果value1<value2壓入-1.

fcmp<cond> l g dcomp<cond>

這組指令用來比較兩個(gè)float或者double類型的值,然后然后將結(jié)果值壓入棧中:如果value1>value2,壓入1,如果value1==value2,壓入0,如果value1<value2壓入-1. 指令可以以l或者g結(jié)尾,不同之處在于它們是如何處理NaN的。fcmpg和dcmpg指令把整數(shù)1壓入操作數(shù)棧,而fcmpl和dcmpl把-1壓入操作數(shù)棧。這確保了比較兩個(gè)值的時(shí)候,如果其中一個(gè)不是數(shù)字(Not A Number, NaN),比較的結(jié)果不會(huì)相等。比如判斷if x > y(x和y都是浮點(diǎn)數(shù)),就會(huì)用的fcmpl,如果其中一個(gè)值是NaN的話,-1會(huì)被壓入棧頂,下一條指令則是ifle,如果分支小于0則跳轉(zhuǎn)。因此如果有一個(gè)是NaN的話,ifle會(huì)跳過if塊,不讓它執(zhí)行。

instanceof

如果棧頂對(duì)象的類型是指定的類的話,則將1壓入棧中。這個(gè)指令的操作數(shù)指定的是某個(gè)類型在常量池的序號(hào)。如果對(duì)象為空或者不是對(duì)應(yīng)的類型,則將0壓入操作數(shù)棧中。

if<cond>

將棧頂值和0進(jìn)行比較,如果條件為真,則跳轉(zhuǎn)到指定的分支繼續(xù)執(zhí)行。這些指令通常用于較復(fù)雜的條件判斷中,在一些單條指令無法完成的情況。比如驗(yàn)證方法調(diào)用的返回值。

switch語句
Java switch表達(dá)式的類型只能是char,byte,short,int,Character, Byte, Short,Integer,String或者enum。JVM為了支持switch語句,用了兩個(gè)特殊的指令,叫做tableSwitch和lookupswitch,它們都只能操作整型數(shù)值。只能使用整型并不影響,因?yàn)閏har,byte,short和enum都可以提升成int類型。Java7開始支持String類型,下面我們會(huì)介紹到。tableswitch操作會(huì)比較快一些,不過它消耗的內(nèi)存會(huì)更多。tableswitch會(huì)列出case分支里面最大值和最小值之間的所有值,如果判斷的值不在這個(gè)范圍內(nèi)則直接跳轉(zhuǎn)到default塊執(zhí)行,case中沒有的值也會(huì)被列出,不過它們同樣指向的是default塊。拿下面的這個(gè)switch語句作為例子:
public int simpleSwitch(int intOne) {
switch (intOne) {
case 0:
return 3;
case 1:
return 2;
case 4:
return 1;
default:
return -1;
}
}

編譯后會(huì)生成如下的字節(jié)碼
0: iload_1
1: tableswitch {
default: 42
min: 0
max: 4
0: 36
1: 38
2: 42
3: 42
4: 40
}
36: iconst_3
37: ireturn
38: iconst_2
39: ireturn
40: iconst_1
41: ireturn
42: iconst_m1
43: ireturn

tableswitch指令里0,1,4的值和代碼里的case語句一一對(duì)應(yīng),它們指向的是對(duì)應(yīng)代碼塊的字節(jié)碼。tableswitch指令同樣有2,3的值,但代碼中并沒有對(duì)應(yīng)的case語句,它們指向的是default代碼塊。當(dāng)這條指令執(zhí)行的時(shí)候,會(huì)判斷操作數(shù)棧頂?shù)闹凳欠裨谧畲笾岛妥钚≈抵g。如果不在的話,直接跳去default分支,也就是上面的42行處的字節(jié)碼。為了確保能找到default分支,它都是出現(xiàn)在tableswitch指令的第一個(gè)字節(jié)(如果需要內(nèi)存對(duì)齊的話,則在補(bǔ)齊了之后的第一個(gè)字節(jié))。如果棧頂?shù)闹翟谧畲笞钚≈档姆秶鷥?nèi),則用它作為tableswtich內(nèi)部的索引,定位到應(yīng)該跳轉(zhuǎn)的分支。比如1的話,就會(huì)跳轉(zhuǎn)至38行處繼續(xù)執(zhí)行。下圖會(huì)演示這條指令是如何執(zhí)行的:



如果case語句里面的值取值范圍太廣了(也就是太分散了)這個(gè)方法就不太好了,因?yàn)樗加玫膬?nèi)存太多了。因此當(dāng)switch的case條件里面的值比較分散的時(shí)候,就會(huì)使用lookupswitch指令。這個(gè)指令會(huì)列出case語句里的所有跳轉(zhuǎn)的分支,但它沒有列出所有可能的值。當(dāng)執(zhí)行這條指令的時(shí)候,棧頂?shù)闹禃?huì)和lookupswitch里的每個(gè)值進(jìn)行比較,來確定要跳轉(zhuǎn)的分支。執(zhí)行l(wèi)ookupswitch指令的時(shí)候,JVM會(huì)在列表中查找匹配的元素,這和tableswitch比起來要慢一些,因?yàn)閠ableswitch直接用索引就定位到正確的位置了。當(dāng)switch語句編譯的時(shí)候,編譯器必須去權(quán)衡內(nèi)存的使用和性能的影響,來決定到底該使用哪條指令。下面的代碼,編譯器會(huì)生成lookupswitch語句:
public int simpleSwitch(int intOne) {
switch (intOne) {
case 10:
return 1;
case 20:
return 2;
case 30:
return 3;
default:
return -1;
}
}

生成后的字節(jié)碼如下:
0: iload_1
1: lookupswitch {
default: 42
count: 3
10: 36
20: 38
30: 40
}
36: iconst_1
37: ireturn
38: iconst_2
39: ireturn
40: iconst_3
41: ireturn
42: iconst_m1
43: ireturn

為了確保搜索算法的高效(得比線性查找要快),這里會(huì)提供列表的長(zhǎng)度,同時(shí)匹配的元素也是排好序的。下圖演示了lookupswitch指令是如何執(zhí)行的。



從Java7開始,switch語句增加了對(duì)String類型的支持。不過字節(jié)碼中的switch指令還是只支持int類型,并沒有增加對(duì)其它類型的支持。事實(shí)上switch語句對(duì)String的支持是分成兩個(gè)步驟來完成的。首先,將每個(gè)case語句里的值的hashCode和操作數(shù)棧頂?shù)闹担ㄗg注:也就是switch里面的那個(gè)值,這個(gè)值會(huì)先壓入棧頂)進(jìn)行比較。這個(gè)可以通過lookupswitch或者是tableswitch指令來完成。結(jié)果會(huì)路由到某個(gè)分支上,然后調(diào)用String.equlals來判斷是否確實(shí)匹配。最后根據(jù)equals返回的結(jié)果,再用一個(gè)tableswitch指令來路由到具體的case分支上去執(zhí)行。
public int simpleSwitch(String stringOne) {
switch (stringOne) {
case "a":
return 0;
case "b":
return 2;
case "c":
return 3;
default:
return 4;
}
}

這個(gè)字符串的switch語句會(huì)生成下面的字節(jié)碼:
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

這段字節(jié)碼所在的class文件里面,會(huì)包含如下的一個(gè)常量池。關(guān)于常量池可以看下JVM內(nèi)部細(xì)節(jié)中的運(yùn)行時(shí)常量池一節(jié)。
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

注意,在執(zhí)行這個(gè)switch語句的時(shí)候,用到了兩個(gè)tableswitch指令,同時(shí)還有數(shù)個(gè)invokevirtual指令,這個(gè)是用來調(diào)用String.equals()方法的。在下一篇文章中關(guān)于方法調(diào)用的那節(jié),會(huì)詳細(xì)介紹到這個(gè)invokevirtual指令。下圖演示了輸入為”b”的情況下,這個(gè)swith語句是如何執(zhí)行的。



如果有幾個(gè)分支的hashcode是一樣的話,比如說“FB”和”Ea”,它們的hashCode都是28,得簡(jiǎn)單的調(diào)整下equals方法的處理流程來進(jìn)行處理。在下面的這個(gè)例子中,34行處的字節(jié)碼ifeg 42會(huì)跳轉(zhuǎn)到另一個(gè)String.equals方法調(diào)用,而不是像前面那樣執(zhí)行l(wèi)ookupswitch指令,因?yàn)榍懊娴哪莻€(gè)例子中hashCode沒有沖突。(譯注:這里一般容易弄混淆,認(rèn)為ifeq是字符串相等,為什么要跳到下一處繼續(xù)比較字符串?其實(shí)ifeq是判斷棧頂元素是否和0相等,而棧頂?shù)闹稻褪荢tring.equals的返回值,而true,也就是相等,返回的是1,false返回的是0,因此ifeq為真的時(shí)候表明返回的是false,這會(huì)兒就應(yīng)該繼續(xù)進(jìn)行下一個(gè)字符串的比較)
public int simpleSwitch(String stringOne) {
switch (stringOne) {
case "FB":
return 0;
case "Ea":
return 2;
default:
return 4;
}
}

這段代碼會(huì)生成下面的字節(jié)碼:
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

循環(huán)語句

if-else和switch這些條件流程控制語句都是先通過一條指令比較兩個(gè)值,然后跳轉(zhuǎn)到某個(gè)分支去執(zhí)行。
for循環(huán)和while循環(huán)這些語句也類似,只不過它們通常都包含一個(gè)goto指令,使得字節(jié)碼能夠循環(huán)執(zhí)行。do-while循環(huán)則不需要goto指令,因?yàn)樗鼈兊臈l件判斷指令是放在循環(huán)體的最后來執(zhí)行。
有一些操作碼能在單條指令內(nèi)完成整數(shù)或者引用的比較,然后根據(jù)結(jié)果跳轉(zhuǎn)到某個(gè)分支繼續(xù)執(zhí)行。而比較double,long,float這些類型則需要兩條指令。首先會(huì)將兩個(gè)值進(jìn)行比較,然后根據(jù)結(jié)果把1,-1,0壓入操作數(shù)棧中。然后再根據(jù)棧頂?shù)闹凳谴笥谛∮诨蛘叩扔?,來決定下一步要執(zhí)行的指令的位置。這些指令在上一篇文章中有詳細(xì)的介紹。

while循環(huán)

while循環(huán)包含條件跳轉(zhuǎn)指令比如if_icmpge 或者if_icmplt(前面有介紹)以及goto指令。如果判斷條件不滿足的話,會(huì)跳轉(zhuǎn)到循環(huán)體后的第一條指令繼續(xù)執(zhí)行,循環(huán)結(jié)束(譯注:這里判斷條件和代碼中的正好相反,如代碼中是i<2,字節(jié)碼內(nèi)是i>=2,從字節(jié)碼的角度看,是滿足條件后循環(huán)中止)。循環(huán)體的末尾是一條goto指令,它會(huì)跳轉(zhuǎn)到循環(huán)開始的地方繼續(xù)執(zhí)行,直到分支跳轉(zhuǎn)的條件滿足才終止。
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指令會(huì)判斷局部變量區(qū)中的1號(hào)位的變量(也就是i,譯注:局部變量區(qū)從0開始計(jì)數(shù),第0位是this)是否大于等于2,如果不是繼續(xù)執(zhí)行,如果是的話跳轉(zhuǎn)到13行處,結(jié)束循環(huán)。goto指令使得循環(huán)可以繼續(xù)執(zhí)行,直到條件判斷為真,這個(gè)時(shí)候會(huì)跳轉(zhuǎn)到緊挨著循環(huán)體后邊的return指令處。iinc是少數(shù)的幾條能直接更新局部變量區(qū)里的變量的指令之一,它不用把值壓到操作數(shù)棧里面就能直接進(jìn)行操作。這里iinc指令把第1個(gè)局部變量(譯注:第0個(gè)是this)自增1。



for循環(huán)和while循環(huán)在字節(jié)碼里的格式是一樣的。這并不奇怪,因?yàn)槊總€(gè)while循環(huán)都可以很容易改寫成一個(gè)for循環(huán)。比如上面的while循環(huán)就可以改寫成下面的for循環(huán),當(dāng)然了它們輸出的字節(jié)碼也是一樣的:
public void forLoop() {
for(int i = 0; i < 2; i++) {

}

}

do-while循環(huán)

do-while循環(huán)和for循環(huán),while循環(huán)非常類似,除了一點(diǎn),它是不需要goto指令的,因?yàn)闂l件跳轉(zhuǎn)指令在循環(huán)體的末尾,可以用它來跳轉(zhuǎn)回循環(huán)體的起始處。
public void doWhileLoop() {
int i = 0;
do {
i++;
} while (i < 2);
}

這會(huì)生成如下的字節(jié)碼:
0: iconst_0
1: istore_1
2: iinc 1, 1
5: iload_1
6: iconst_2
7: if_icmplt 2
10: return


本文最早發(fā)表于本人博客:http://it.deepinmind.com

指令碼 助記符 說明

0x00 nop 什么都不做

0x01 aconst_null 將null推送至棧頂

0x02 iconst_m1 將int型-1推送至棧頂

0x03 iconst_0 將int型0推送至棧頂

0x04 iconst_1 將int型1推送至棧頂

0x05 iconst_2 將int型2推送至棧頂

0x06 iconst_3 將int型3推送至棧頂

0x07 iconst_4 將int型4推送至棧頂

0x08 iconst_5 將int型5推送至棧頂

0x09 lconst_0 將long型0推送至棧頂

0x0a lconst_1 將long型1推送至棧頂

0x0b fconst_0 將float型0推送至棧頂

0x0c fconst_1 將float型1推送至棧頂

0x0d fconst_2 將float型2推送至棧頂

0x0e dconst_0 將double型0推送至棧頂

0x0f dconst_1 將double型1推送至棧頂

0x10 bipush 將單字節(jié)的常量值(-128~127)推送至棧頂

0x11 sipush 將一個(gè)短整型常量值(-32768~32767)推送至棧頂

0x12 ldc 將int, float或String型常量值從常量池中推送至棧頂

0x13 ldc_w 將int, float或String型常量值從常量池中推送至棧頂(寬索引)

0x14 ldc2_w 將long或double型常量值從常量池中推送至棧頂(寬索引)

0x15 iload 將指定的int型本地變量推送至棧頂

0x16 lload 將指定的long型本地變量推送至棧頂

0x17 fload 將指定的float型本地變量推送至棧頂

0x18 dload 將指定的double型本地變量推送至棧頂

0x19 aload 將指定的引用類型本地變量推送至棧頂

0x1a iload_0 將第一個(gè)int型本地變量推送至棧頂

0x1b iload_1 將第二個(gè)int型本地變量推送至棧頂

0x1c iload_2 將第三個(gè)int型本地變量推送至棧頂

0x1d iload_3 將第四個(gè)int型本地變量推送至棧頂

0x1e lload_0 將第一個(gè)long型本地變量推送至棧頂

0x1f lload_1 將第二個(gè)long型本地變量推送至棧頂

0x20 lload_2 將第三個(gè)long型本地變量推送至棧頂

0x21 lload_3 將第四個(gè)long型本地變量推送至棧頂

0x22 fload_0 將第一個(gè)float型本地變量推送至棧頂

0x23 fload_1 將第二個(gè)float型本地變量推送至棧頂

0x24 fload_2 將第三個(gè)float型本地變量推送至棧頂

0x25 fload_3 將第四個(gè)float型本地變量推送至棧頂

0x26 dload_0 將第一個(gè)double型本地變量推送至棧頂

0x27 dload_1 將第二個(gè)double型本地變量推送至棧頂

0x28 dload_2 將第三個(gè)double型本地變量推送至棧頂

0x29 dload_3 將第四個(gè)double型本地變量推送至棧頂

0x2a aload_0 將第一個(gè)引用類型本地變量推送至棧頂

0x2b aload_1 將第二個(gè)引用類型本地變量推送至棧頂

0x2c aload_2 將第三個(gè)引用類型本地變量推送至棧頂

0x2d aload_3 將第四個(gè)引用類型本地變量推送至棧頂

0x2e iaload 將int型數(shù)組指定索引的值推送至棧頂

0x2f laload 將long型數(shù)組指定索引的值推送至棧頂

0x30 faload 將float型數(shù)組指定索引的值推送至棧頂

0x31 daload 將double型數(shù)組指定索引的值推送至棧頂

0x32 aaload 將引用型數(shù)組指定索引的值推送至棧頂

0x33 baload 將boolean或byte型數(shù)組指定索引的值推送至棧頂

0x34 caload 將char型數(shù)組指定索引的值推送至棧頂

0x35 saload 將short型數(shù)組指定索引的值推送至棧頂

0x36 istore 將棧頂int型數(shù)值存入指定本地變量

0x37 lstore 將棧頂long型數(shù)值存入指定本地變量

0x38 fstore 將棧頂float型數(shù)值存入指定本地變量

0x39 dstore 將棧頂double型數(shù)值存入指定本地變量

0x3a astore 將棧頂引用型數(shù)值存入指定本地變量

0x3b istore_0 將棧頂int型數(shù)值存入第一個(gè)本地變量

0x3c istore_1 將棧頂int型數(shù)值存入第二個(gè)本地變量

0x3d istore_2 將棧頂int型數(shù)值存入第三個(gè)本地變量

0x3e istore_3 將棧頂int型數(shù)值存入第四個(gè)本地變量

0x3f lstore_0 將棧頂long型數(shù)值存入第一個(gè)本地變量

0x40 lstore_1 將棧頂long型數(shù)值存入第二個(gè)本地變量

0x41 lstore_2 將棧頂long型數(shù)值存入第三個(gè)本地變量

0x42 lstore_3 將棧頂long型數(shù)值存入第四個(gè)本地變量

0x43 fstore_0 將棧頂float型數(shù)值存入第一個(gè)本地變量

0x44 fstore_1 將棧頂float型數(shù)值存入第二個(gè)本地變量

0x45 fstore_2 將棧頂float型數(shù)值存入第三個(gè)本地變量

0x46 fstore_3 將棧頂float型數(shù)值存入第四個(gè)本地變量

0x47 dstore_0 將棧頂double型數(shù)值存入第一個(gè)本地變量

0x48 dstore_1 將棧頂double型數(shù)值存入第二個(gè)本地變量

0x49 dstore_2 將棧頂double型數(shù)值存入第三個(gè)本地變量

0x4a dstore_3 將棧頂double型數(shù)值存入第四個(gè)本地變量

0x4b astore_0 將棧頂引用型數(shù)值存入第一個(gè)本地變量

0x4c astore_1 將棧頂引用型數(shù)值存入第二個(gè)本地變量

0x4d astore_2 將棧頂引用型數(shù)值存入第三個(gè)本地變量

0x4e astore_3 將棧頂引用型數(shù)值存入第四個(gè)本地變量

0x4f iastore 將棧頂int型數(shù)值存入指定數(shù)組的指定索引位置

0x50 lastore 將棧頂long型數(shù)值存入指定數(shù)組的指定索引位置

0x51 fastore 將棧頂float型數(shù)值存入指定數(shù)組的指定索引位置

0x52 dastore 將棧頂double型數(shù)值存入指定數(shù)組的指定索引位置

0x53 aastore 將棧頂引用型數(shù)值存入指定數(shù)組的指定索引位置

0x54 bastore 將棧頂boolean或byte型數(shù)值存入指定數(shù)組的指定索引位置

0x55 castore 將棧頂char型數(shù)值存入指定數(shù)組的指定索引位置

0x56 sastore 將棧頂short型數(shù)值存入指定數(shù)組的指定索引位置

0x57 pop 將棧頂數(shù)值彈出 (數(shù)值不能是long或double類型的)

0x58 pop2 將棧頂?shù)囊粋€(gè)(long或double類型的)或兩個(gè)數(shù)值彈出(其它)

0x59 dup 復(fù)制棧頂數(shù)值并將復(fù)制值壓入棧頂

0x5a dup_x1 復(fù)制棧頂數(shù)值并將兩個(gè)復(fù)制值壓入棧頂

0x5b dup_x2 復(fù)制棧頂數(shù)值并將三個(gè)(或兩個(gè))復(fù)制值壓入棧頂

0x5c dup2 復(fù)制棧頂一個(gè)(long或double類型的)或兩個(gè)(其它)數(shù)值并將復(fù)制值壓入棧頂

0x5d dup2_x1 <待補(bǔ)充>

0x5e dup2_x2 <待補(bǔ)充>

0x5f swap 將棧最頂端的兩個(gè)數(shù)值互換(數(shù)值不能是long或double類型的)

0x60 iadd 將棧頂兩int型數(shù)值相加并將結(jié)果壓入棧頂

0x61 ladd 將棧頂兩long型數(shù)值相加并將結(jié)果壓入棧頂

0x62 fadd 將棧頂兩float型數(shù)值相加并將結(jié)果壓入棧頂

0x63 dadd 將棧頂兩double型數(shù)值相加并將結(jié)果壓入棧頂

0x64 isub 將棧頂兩int型數(shù)值相減并將結(jié)果壓入棧頂

0x65 lsub 將棧頂兩long型數(shù)值相減并將結(jié)果壓入棧頂

0x66 fsub 將棧頂兩float型數(shù)值相減并將結(jié)果壓入棧頂

0x67 dsub 將棧頂兩double型數(shù)值相減并將結(jié)果壓入棧頂

0x68 imul 將棧頂兩int型數(shù)值相乘并將結(jié)果壓入棧頂

0x69 lmul 將棧頂兩long型數(shù)值相乘并將結(jié)果壓入棧頂

0x6a fmul 將棧頂兩float型數(shù)值相乘并將結(jié)果壓入棧頂

0x6b dmul 將棧頂兩double型數(shù)值相乘并將結(jié)果壓入棧頂

0x6c idiv 將棧頂兩int型數(shù)值相除并將結(jié)果壓入棧頂

0x6d ldiv 將棧頂兩long型數(shù)值相除并將結(jié)果壓入棧頂

0x6e fdiv 將棧頂兩float型數(shù)值相除并將結(jié)果壓入棧頂

0x6f ddiv 將棧頂兩double型數(shù)值相除并將結(jié)果壓入棧頂

0x70 irem 將棧頂兩int型數(shù)值作取模運(yùn)算并將結(jié)果壓入棧頂

0x71 lrem 將棧頂兩long型數(shù)值作取模運(yùn)算并將結(jié)果壓入棧頂

0x72 frem 將棧頂兩float型數(shù)值作取模運(yùn)算并將結(jié)果壓入棧頂

0x73 drem 將棧頂兩double型數(shù)值作取模運(yùn)算并將結(jié)果壓入棧頂

0x74 ineg 將棧頂int型數(shù)值取負(fù)并將結(jié)果壓入棧頂

0x75 lneg 將棧頂long型數(shù)值取負(fù)并將結(jié)果壓入棧頂

0x76 fneg 將棧頂float型數(shù)值取負(fù)并將結(jié)果壓入棧頂

0x77 dneg 將棧頂double型數(shù)值取負(fù)并將結(jié)果壓入棧頂

0x78 ishl 將int型數(shù)值左移位指定位數(shù)并將結(jié)果壓入棧頂

0x79 lshl 將long型數(shù)值左移位指定位數(shù)并將結(jié)果壓入棧頂

0x7a ishr 將int型數(shù)值右(符號(hào))移位指定位數(shù)并將結(jié)果壓入棧頂

0x7b lshr 將long型數(shù)值右(符號(hào))移位指定位數(shù)并將結(jié)果壓入棧頂

0x7c iushr 將int型數(shù)值右(無符號(hào))移位指定位數(shù)并將結(jié)果壓入棧頂

0x7d lushr 將long型數(shù)值右(無符號(hào))移位指定位數(shù)并將結(jié)果壓入棧頂

0x7e iand 將棧頂兩int型數(shù)值作“按位與”并將結(jié)果壓入棧頂

0x7f land 將棧頂兩long型數(shù)值作“按位與”并將結(jié)果壓入棧頂

0x80 ior 將棧頂兩int型數(shù)值作“按位或”并將結(jié)果壓入棧頂

0x81 lor 將棧頂兩long型數(shù)值作“按位或”并將結(jié)果壓入棧頂

0x82 ixor 將棧頂兩int型數(shù)值作“按位異或”并將結(jié)果壓入棧頂

0x83 lxor 將棧頂兩long型數(shù)值作“按位異或”并將結(jié)果壓入棧頂

0x84 iinc 將指定int型變量增加指定值(i++, i--, i+=2)

0x85 i2l 將棧頂int型數(shù)值強(qiáng)制轉(zhuǎn)換成long型數(shù)值并將結(jié)果壓入棧頂

0x86 i2f 將棧頂int型數(shù)值強(qiáng)制轉(zhuǎn)換成float型數(shù)值并將結(jié)果壓入棧頂

0x87 i2d 將棧頂int型數(shù)值強(qiáng)制轉(zhuǎn)換成double型數(shù)值并將結(jié)果壓入棧頂

0x88 l2i 將棧頂long型數(shù)值強(qiáng)制轉(zhuǎn)換成int型數(shù)值并將結(jié)果壓入棧頂

0x89 l2f 將棧頂long型數(shù)值強(qiáng)制轉(zhuǎn)換成float型數(shù)值并將結(jié)果壓入棧頂

0x8a l2d 將棧頂long型數(shù)值強(qiáng)制轉(zhuǎn)換成double型數(shù)值并將結(jié)果壓入棧頂

0x8b f2i 將棧頂float型數(shù)值強(qiáng)制轉(zhuǎn)換成int型數(shù)值并將結(jié)果壓入棧頂

0x8c f2l 將棧頂float型數(shù)值強(qiáng)制轉(zhuǎn)換成long型數(shù)值并將結(jié)果壓入棧頂

0x8d f2d 將棧頂float型數(shù)值強(qiáng)制轉(zhuǎn)換成double型數(shù)值并將結(jié)果壓入棧頂

0x8e d2i 將棧頂double型數(shù)值強(qiáng)制轉(zhuǎn)換成int型數(shù)值并將結(jié)果壓入棧頂

0x8f d2l 將棧頂double型數(shù)值強(qiáng)制轉(zhuǎn)換成long型數(shù)值并將結(jié)果壓入棧頂

0x90 d2f 將棧頂double型數(shù)值強(qiáng)制轉(zhuǎn)換成float型數(shù)值并將結(jié)果壓入棧頂

0x91 i2b 將棧頂int型數(shù)值強(qiáng)制轉(zhuǎn)換成byte型數(shù)值并將結(jié)果壓入棧頂

0x92 i2c 將棧頂int型數(shù)值強(qiáng)制轉(zhuǎn)換成char型數(shù)值并將結(jié)果壓入棧頂

0x93 i2s 將棧頂int型數(shù)值強(qiáng)制轉(zhuǎn)換成short型數(shù)值并將結(jié)果壓入棧頂

0x94 lcmp 比較棧頂兩long型數(shù)值大小,并將結(jié)果(1,0,-1)壓入棧頂

0x95 fcmpl 比較棧頂兩float型數(shù)值大小,并將結(jié)果(1,0,-1)壓入棧頂;當(dāng)其中一個(gè)數(shù)值為NaN時(shí),將-1壓入棧頂

0x96 fcmpg 比較棧頂兩float型數(shù)值大小,并將結(jié)果(1,0,-1)壓入棧頂;當(dāng)其中一個(gè)數(shù)值為NaN時(shí),將1壓入棧頂

0x97 dcmpl 比較棧頂兩double型數(shù)值大小,并將結(jié)果(1,0,-1)壓入棧頂;當(dāng)其中一個(gè)數(shù)值為NaN時(shí),將-1壓入棧頂

0x98 dcmpg 比較棧頂兩double型數(shù)值大小,并將結(jié)果(1,0,-1)壓入棧頂;當(dāng)其中一個(gè)數(shù)值為NaN時(shí),將1壓入棧頂

0x99 ifeq 當(dāng)棧頂int型數(shù)值等于0時(shí)跳轉(zhuǎn)

0x9a ifne 當(dāng)棧頂int型數(shù)值不等于0時(shí)跳轉(zhuǎn)

0x9b iflt 當(dāng)棧頂int型數(shù)值小于0時(shí)跳轉(zhuǎn)

0x9c ifge 當(dāng)棧頂int型數(shù)值大于等于0時(shí)跳轉(zhuǎn)

0x9d ifgt 當(dāng)棧頂int型數(shù)值大于0時(shí)跳轉(zhuǎn)

0x9e ifle 當(dāng)棧頂int型數(shù)值小于等于0時(shí)跳轉(zhuǎn)

0x9f if_icmpeq 比較棧頂兩int型數(shù)值大小,當(dāng)結(jié)果等于0時(shí)跳轉(zhuǎn)

0xa0 if_icmpne 比較棧頂兩int型數(shù)值大小,當(dāng)結(jié)果不等于0時(shí)跳轉(zhuǎn)

0xa1 if_icmplt 比較棧頂兩int型數(shù)值大小,當(dāng)結(jié)果小于0時(shí)跳轉(zhuǎn)

0xa2 if_icmpge 比較棧頂兩int型數(shù)值大小,當(dāng)結(jié)果大于等于0時(shí)跳轉(zhuǎn)

0xa3 if_icmpgt 比較棧頂兩int型數(shù)值大小,當(dāng)結(jié)果大于0時(shí)跳轉(zhuǎn)

0xa4 if_icmple 比較棧頂兩int型數(shù)值大小,當(dāng)結(jié)果小于等于0時(shí)跳轉(zhuǎn)

0xa5 if_acmpeq 比較棧頂兩引用型數(shù)值,當(dāng)結(jié)果相等時(shí)跳轉(zhuǎn)

0xa6 if_acmpne 比較棧頂兩引用型數(shù)值,當(dāng)結(jié)果不相等時(shí)跳轉(zhuǎn)

0xa7 goto 無條件跳轉(zhuǎn)

0xa8 jsr 跳轉(zhuǎn)至指定16位offset位置,并將jsr下一條指令地址壓入棧頂

0xa9 ret 返回至本地變量指定的index的指令位置(一般與jsr, jsr_w聯(lián)合使用)

0xaa tableswitch 用于switch條件跳轉(zhuǎn),case值連續(xù)(可變長(zhǎng)度指令)

0xab lookupswitch 用于switch條件跳轉(zhuǎn),case值不連續(xù)(可變長(zhǎng)度指令)

0xac ireturn 從當(dāng)前方法返回int

0xad lreturn 從當(dāng)前方法返回long

0xae freturn 從當(dāng)前方法返回float

0xaf dreturn 從當(dāng)前方法返回double

0xb0 areturn 從當(dāng)前方法返回對(duì)象引用

0xb1 return 從當(dāng)前方法返回void

0xb2 getstatic 獲取指定類的靜態(tài)域,并將其值壓入棧頂

0xb3 putstatic 為指定的類的靜態(tài)域賦值

0xb4 getfield 獲取指定類的實(shí)例域,并將其值壓入棧頂

0xb5 putfield 為指定的類的實(shí)例域賦值

0xb6 invokevirtual 調(diào)用實(shí)例方法

0xb7 invokespecial 調(diào)用超類構(gòu)造方法,實(shí)例初始化方法,私有方法

0xb8 invokestatic 調(diào)用靜態(tài)方法

0xb9 invokeinterface 調(diào)用接口方法

0xba --

0xbb new 創(chuàng)建一個(gè)對(duì)象,并將其引用值壓入棧頂

0xbc newarray 創(chuàng)建一個(gè)指定原始類型(如int, float, char…)的數(shù)組,并將其引用值壓入棧頂

0xbd anewarray 創(chuàng)建一個(gè)引用型(如類,接口,數(shù)組)的數(shù)組,并將其引用值壓入棧頂

0xbe arraylength 獲得數(shù)組的長(zhǎng)度值并壓入棧頂

0xbf athrow 將棧頂?shù)漠惓伋?/p>

0xc0 checkcast 檢驗(yàn)類型轉(zhuǎn)換,檢驗(yàn)未通過將拋出ClassCastException

0xc1 instanceof 檢驗(yàn)對(duì)象是否是指定的類的實(shí)例,如果是將1壓入棧頂,否則將0壓入棧頂

0xc2 monitorenter 獲得對(duì)象的鎖,用于同步方法或同步塊

0xc3 monitorexit 釋放對(duì)象的鎖,用于同步方法或同步塊

0xc4 wide <待補(bǔ)充>

0xc5 multianewarray 創(chuàng)建指定類型和指定維度的多維數(shù)組(執(zhí)行該指令時(shí),操作棧中必須包含各維度的長(zhǎng)度值),并將其引用值壓入棧頂

0xc6 ifnull 為null時(shí)跳轉(zhuǎn)

0xc7 ifnonnull 不為null時(shí)跳轉(zhuǎn)

0xc8 goto_w 無條件跳轉(zhuǎn)(寬索引)

0xc9 jsr_w 跳轉(zhuǎn)至指定32位offset位置,并將jsr_w下一條指令地址壓入棧頂

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容