天天看點

我要悄悄學習 Java 位元組碼指令,在成為技術大佬的路上一去不複返(4)

06、操作數棧管理指令

常見的操作數棧管理指令有 pop、dup 和 swap。

将一個或兩個元素從棧頂彈出,并且直接廢棄,比如 pop,pop2;

複制棧頂的一個或兩個數值并将其重新壓入棧頂,比如 dup,dup2,dup_×1,dup2_×1,dup_×2,dup2_×2;

将棧最頂端的兩個槽中的數值交換位置,比如 swap。

這些指令不需要指明資料類型,因為是按照位置壓入和彈出的。

舉例來說。

public class Dup {
    int age;
    public int incAndGet() {
        return ++age;
    }
}      

通過 jclasslib 看一下 incAndGet() 方法的位元組碼指令。

我要悄悄學習 Java 位元組碼指令,在成為技術大佬的路上一去不複返(4)

aload_0:将 this 入棧。

dup:複制棧頂的 this。

getfield #2:将常量池中下标為 2 的常量加載到棧上,同時将一個 this 出棧。

iconst_1:将常量 1 入棧。

iadd:将棧頂的兩個值相加後出棧,并将結果放回棧上。

dup_x1:複制棧頂的元素,并将其插入 this 下面。

putfield #2: 将棧頂的兩個元素出棧,并将其指派給字段 age。

ireturn:将棧頂的元素出棧傳回。

07、控制轉移指令

控制轉移指令包括:

比較指令,比較棧頂的兩個元素的大小,并将比較結果入棧。

條件跳轉指令,通常和比較指令一塊使用,在條件跳轉指令執行前,一般先用比較指令進行棧頂元素的比較,然後進行條件跳轉。

比較條件轉指令,類似于比較指令和條件跳轉指令的結合體,它将比較和跳轉兩個步驟合二為一。

多條件分支跳轉指令,專為 switch-case 語句設計的。

無條件跳轉指令,目前主要是 goto 指令。

1)比較指令

比較指令有:dcmpg,dcmpl、fcmpg、fcmpl、lcmp,指令的第一個字母代表的含義分别是 double、float、long。注意,沒有 int 類型。

對于 double 和 float 來說,由于 NaN 的存在,有兩個版本的比較指令。拿 float 來說,有 fcmpg 和 fcmpl,差別在于,如果遇到 NaN,fcmpg 會将 1 壓入棧,fcmpl 會将 -1 壓入棧。

public void lcmp(long a, long b) {

   if(a > b){}

}

通過 jclasslib 看一下 lcmp() 方法的位元組碼指令。

我要悄悄學習 Java 位元組碼指令,在成為技術大佬的路上一去不複返(4)

lcmp 用于兩個 long 型的資料進行比較。

2)條件跳轉指令

我要悄悄學習 Java 位元組碼指令,在成為技術大佬的路上一去不複返(4)

這些指令都會接收兩個位元組的操作數,它們的統一含義是,彈出棧頂元素,測試它是否滿足某一條件,滿足的話,跳轉到對應位置。

對于 long、float 和 double 類型的條件分支比較,會先執行比較指令傳回一個整形值到操作數棧中後再執行 int 類型的條件跳轉指令。

對于 boolean、byte、char、short,以及 int,則直接使用條件跳轉指令來完成。

public void fi() {
    int a = 0;
    if (a == 0) {
        a = 10;
    } else {
        a = 20;
    }
}      

通過 jclasslib 看一下 fi() 方法的位元組碼指令。

我要悄悄學習 Java 位元組碼指令,在成為技術大佬的路上一去不複返(4)

3 ifne 12 (+9) 的意思是,如果棧頂的元素不等于 0,跳轉到第 12(3+9)行 12 bipush 20。

3)比較條件轉指令

我要悄悄學習 Java 位元組碼指令,在成為技術大佬的路上一去不複返(4)

字首“if_”後,以字元“i”開頭的指令針對 int 型整數進行操作,以字元“a”開頭的指令表示對象的比較。

public void compare() {
    int i = 10;
    int j = 20;
    System.out.println(i > j);
}      

通過 jclasslib 看一下 compare() 方法的位元組碼指令。

我要悄悄學習 Java 位元組碼指令,在成為技術大佬的路上一去不複返(4)

11 if_icmple 18 (+7) 的意思是,如果棧頂的兩個 int 類型的數值比較的話,如果前者小于後者時跳轉到第 18 行(11+7)。

4)多條件分支跳轉指令

主要有 tableswitch 和 lookupswitch,前者要求多個條件分支值是連續的,它内部隻存放起始值和終止值,以及若幹個跳轉偏移量,通過給定的操作數 index,可以立即定位到跳轉偏移量位置,是以效率比較高;後者内部存放着各個離散的 case-offset 對,每次執行都要搜尋全部的 case-offset 對,找到比對的 case 值,并根據對應的 offset 計算跳轉位址,是以效率較低。

public void switchTest(int select) {
    int num;
    switch (select) {
        case 1:
            num = 10;
            break;
        case 2:
        case 3:
            num = 30;
            break;
        default:
            num = 40;
    }
}      

通過 jclasslib 看一下 switchTest() 方法的位元組碼指令。

我要悄悄學習 Java 位元組碼指令,在成為技術大佬的路上一去不複返(4)

case 2 的時候沒有 break,是以 case 2 和 case 3 是連續的,用的是 tableswitch。如果等于 1,跳轉到 28 行;如果等于 2 和 3,跳轉到 34 行,如果是 default,跳轉到 40 行。

5)無條件跳轉指令

goto 指令接收兩個位元組的操作數,共同組成一個帶符号的整數,用于指定指令的偏移量,指令執行的目的就是跳轉到偏移量給定的位置處。

前面的例子裡都出現了 goto 的身影,也很好了解。如果指令的偏移量特别大,超出了兩個位元組的範圍,可以使用指令 goto_w,接收 4 個位元組的操作數。

巨人的肩膀:

https://segmentfault.com/a/1190000037628881

除了以上這些指令,還有異常處理指令和同步控制指令,我打算吊一吊大家的胃口,大家可以期待一波~~

(騷操作)

路漫漫其修遠兮,吾将上下而求索

想要走得更遠,Java 位元組碼這塊就必須得硬碰硬地吃透,希望二哥的這些分享可以幫助到大家~

叨逼叨

二哥在 CSDN 上寫了很多 Java 方面的系列文章,有 Java 核心文法、Java 集合架構、Java IO、Java 并發程式設計、Java 虛拟機等,也算是體系完整了。