Java 字節(jié)碼指令袖裕,讓我發(fā)了瘋瘋瘋!

聽我說溉瓶,聽我說急鳄,學 Java 準沒錯,畢竟崗位多薪資高堰酿!但涌進來的人越多疾宏,就意味著越來越卷,要想不被卷到触创,就必須得瘋狂的學習坎藐,學什么呢?Java 字節(jié)碼指令就是一塊硬骨頭哼绑。

有些讀者可能會有這樣的疑惑岩馍,“Java 字節(jié)碼難嗎?Java 虛擬機難嗎抖韩?我能不能學會爸鳌?”

不要擔心茂浮,有二哥在双谆,保證小白也能看得懂!

講良心話席揽,不是我謙虛佃乘,一開始學 Java 字節(jié)碼和 Java 虛擬機方面的知識我也感覺頭大!但硬著頭皮學了一陣子之后驹尼,突然就開竅了趣避,覺得好有意思,尤其是明白了 Java 代碼在底層竟然是這樣執(zhí)行的時候新翎,感覺既膨脹又飄飄然程帕,渾身上下散發(fā)著自信的光芒住练!

我在 簡書 共輸出了 100 多篇 Java 方面的文章,總字數(shù)超過 30 萬字愁拭, 內(nèi)容風趣幽默讲逛、通俗易懂,收獲了很多初學者的認可和支持岭埠,內(nèi)容包括 Java 語法盏混、Java 集合框架、Java 并發(fā)編程惜论、Java 虛擬機等核心內(nèi)容许赃。

image

為了幫助更多的 Java 初學者,我“一怒之下”就把這些文章重新整理并開源到了 GitHub馆类,起名《教妹學 Java》混聊,聽起來是不是就很有趣?

GitHub 開源地址(歡迎 star):https://github.com/itwanger/jmx-java

之前的文章里我也提到了乾巧,Java 官方的虛擬機 Hotspot 是基于棧的句喜,而不是基于寄存器的。

基于棧的優(yōu)點是可移植性更好沟于、指令更短咳胃、實現(xiàn)起來簡單,但不能隨機訪問棧中的元素旷太,完成相同功能所需要的指令數(shù)也比寄存器的要多展懈,需要頻繁的入棧和出棧。

基于寄存器的優(yōu)點是速度快泳秀,有利于程序運行速度的優(yōu)化标沪,但操作數(shù)需要顯式指定榄攀,指令也比較長嗜傅。

Java 字節(jié)碼由操作碼和操作數(shù)組成。

  • 操作碼(Opcode):一個字節(jié)長度(0-255檩赢,意味著指令集的操作碼總數(shù)不可能超過 256 條)吕嘀,代表著某種特定的操作含義。
  • 操作數(shù)(Operands):零個或者多個贞瞒,緊跟在操作碼之后偶房,代表此操作需要的參數(shù)。

由于 Java 虛擬機是基于棧而不是寄存器的結(jié)構(gòu)军浆,所以大多數(shù)指令都只有一個操作碼棕洋。比如 aload_0(將局部變量表中下標為 0 的數(shù)據(jù)壓入操作數(shù)棧中)就只有操作碼沒有操作數(shù),而 invokespecial #1(調(diào)用成員方法或者構(gòu)造方法乒融,并傳遞常量池中下標為 1 的常量)就是由操作碼和操作數(shù)組成的掰盘。

01摄悯、加載與存儲指令

加載(load)和存儲(store)相關(guān)的指令是使用最頻繁的指令,用于將數(shù)據(jù)從棧幀的局部變量表和操作數(shù)棧之間來回傳遞愧捕。

1)將局部變量表中的變量壓入操作數(shù)棧中

  • xload_<n>(x 為 i奢驯、l、f次绘、d瘪阁、a,n 默認為 0 到 3)邮偎,表示將第 n 個局部變量壓入操作數(shù)棧中管跺。
  • xload(x 為 i、l钢猛、f伙菜、d、a)命迈,通過指定參數(shù)的形式贩绕,將局部變量壓入操作數(shù)棧中,當使用這個指令時壶愤,表示局部變量的數(shù)量可能超過了 4 個

解釋一下淑倾。

x 為操作碼助記符,表明是哪一種數(shù)據(jù)類型征椒。見下表所示娇哆。

像 arraylength 指令,沒有操作碼助記符勃救,它沒有代表數(shù)據(jù)類型的特殊字符碍讨,但操作數(shù)只能是一個數(shù)組類型的對象。

大部分的指令都不支持 byte蒙秒、short 和 char勃黍,甚至沒有任何指令支持 boolean 類型。編譯器會將 byte 和 short 類型的數(shù)據(jù)帶符號擴展(Sign-Extend)為 int 類型晕讲,將 boolean 和 char 零位擴展(Zero-Extend)為 int 類型覆获。

舉例來說。

private void load(int age, String name, long birthday, boolean sex) {
    System.out.println(age + name + birthday + sex);
}

通過 jclasslib 看一下 load() 方法(4 個參數(shù))的字節(jié)碼指令瓢省。

  • iload_1:將局部變量表中下標為 1 的 int 變量壓入操作數(shù)棧中羔飞。
  • aload_2:將局部變量表中下標為 2 的引用數(shù)據(jù)類型變量(此時為 String)壓入操作數(shù)棧中驯鳖。
  • lload_3:將局部變量表中下標為 3 的 long 型變量壓入操作數(shù)棧中。
  • iload 5:將局部變量表中下標為 5 的 int 變量(實際為 boolean)壓入操作數(shù)棧中。

通過查看局部變量表就能關(guān)聯(lián)上了硬爆。

2)將常量池中的常量壓入操作數(shù)棧中

根據(jù)數(shù)據(jù)類型和入棧內(nèi)容的不同潮改,此類又可以細分為 const 系列、push 系列和 Idc 指令。

const 系列废亭,用于特殊的常量入棧,要入棧的常量隱含在指令本身具钥。

push 系列豆村,主要包括 bipush 和 sipush,前者接收 8 位整數(shù)作為參數(shù)骂删,后者接收 16 位整數(shù)掌动。

Idc 指令,當 const 和 push 不能滿足的時候宁玫,萬能的 Idc 指令就上場了粗恢,它接收一個 8 位的參數(shù),指向常量池中的索引欧瘪。

  • Idc_w:接收兩個 8 位數(shù)眷射,索引范圍更大。
  • 如果參數(shù)是 long 或者 double佛掖,使用 Idc2_w 指令妖碉。

舉例來說。

public void pushConstLdc() {
    // 范圍 [-1,5]
    int iconst = -1;
    // 范圍 [-128,127]
    int bipush = 127;
    // 范圍 [-32768,32767]
    int sipush= 32767;
    // 其他 int
    int ldc = 32768;
    String aconst = null;
    String IdcString = "沉默王二";
}

通過 jclasslib 看一下 pushConstLdc() 方法的字節(jié)碼指令芥被。

  • iconst_m1:將 -1 入棧欧宜。范圍 [-1,5]。
  • bipush 127:將 127 入棧拴魄。范圍 [-128,127]冗茸。
  • sipush 32767:將 32767 入棧。范圍 [-32768,32767]匹中。
  • ldc #6 <32768>:將常量池中下標為 6 的常量 32768 入棧夏漱。
  • aconst_null:將 null 入棧。
  • ldc #7 <沉默王二>:將常量池中下標為 7 的常量“沉默王二”入棧顶捷。

3)將棧頂?shù)臄?shù)據(jù)出棧并裝入局部變量表中

主要是用來給局部變量賦值挂绰,這類指令主要以 store 的形式存在。

  • xstore_<n>(x 為 i焊切、l扮授、f芳室、d专肪、a,n 默認為 0 到 3)
  • xstore(x 為 i堪侯、l嚎尤、f、d伍宦、a)

明白了 xload_<n> 和 xload芽死,再看 xstore_<n> 和 xstore 就會輕松得多乏梁,作用反了一下而已。

大家來想一個問題关贵,為什么要有 xstore_<n> 和 xload_<n> 呢遇骑?它們的作用和 xstore n、xload n 不是一樣的嗎揖曾?

xstore_<n> 和 xstore n 的區(qū)別在于落萎,前者相當于只有操作碼,占用 1 個字節(jié)炭剪;后者相當于由操作碼和操作數(shù)組成练链,操作碼占 1 個字節(jié),操作數(shù)占 2 個字節(jié)奴拦,一共占 3 個字節(jié)媒鼓。

由于局部變量表中前幾個位置總是非常常用,雖然 xstore_<n>xload_<n> 增加了指令數(shù)量错妖,但字節(jié)碼的體積變小了绿鸣!

舉例來說。

public void store(int age, String name) {
    int temp = age + 2;
    String str = name;
}

通過 jclasslib 看一下 store() 方法的字節(jié)碼指令暂氯。

  • istore_3:從操作數(shù)中彈出一個整數(shù)枚驻,并把它賦值給局部變量表中索引為 3 的變量。
  • astore 4:從操作數(shù)中彈出一個引用數(shù)據(jù)類型株旷,并把它賦值給局部變量表中索引為 4 的變量再登。

通過查看局部變量表就能關(guān)聯(lián)上了。

02晾剖、算術(shù)指令

算術(shù)指令用于對兩個操作數(shù)棧上的值進行某種特定運算锉矢,并把結(jié)果重新壓入操作數(shù)棧〕菥。可以分為兩類:整型數(shù)據(jù)的運算指令和浮點數(shù)據(jù)的運算指令沽损。

需要注意的是,數(shù)據(jù)運算可能會導致溢出循头,比如兩個很大的正整數(shù)相加绵估,很可能會得到一個負數(shù)。但 Java 虛擬機規(guī)范中并沒有對這種情況給出具體結(jié)果卡骂,因此程序是不會顯式報錯的国裳。所以,大家在開發(fā)過程中全跨,如果涉及到較大的數(shù)據(jù)進行加法缝左、乘法運算的時候,一定要注意!

當發(fā)生溢出時渺杉,將會使用有符號的無窮大 Infinity 來表示蛇数;如果某個操作結(jié)果沒有明確的數(shù)學定義的話,將會使用 NaN 值來表示是越。而且所有使用 NaN 作為操作數(shù)的算術(shù)操作耳舅,結(jié)果都會返回 NaN。

舉例來說倚评。

public void infinityNaN() {
    int i = 10;
    double j = i / 0.0;
    System.out.println(j); // Infinity

    double d1 = 0.0;
    double d2 = d1 / 0.0;
    System.out.println(d2); // NaN
}
  • 任何一個非零的數(shù)除以浮點數(shù) 0(注意不是 int 類型)挽放,可以想象結(jié)果是無窮大 Infinity 的。
  • 把這個非零的數(shù)換成 0 的時候蔓纠,結(jié)果又不太好定義辑畦,就用 NaN 值來表示。

Java 虛擬機提供了兩種運算模式

  • 向最接近數(shù)舍入:在進行浮點數(shù)運算時腿倚,所有的結(jié)果都必須舍入到一個適當?shù)木却砍觯皇翘貏e精確的結(jié)果必須舍入為可被表示的最接近的精確值,如果有兩種可表示的形式與該值接近敷燎,將優(yōu)先選擇最低有效位為零的(類似四舍五入)暂筝。
  • 向零舍入:將浮點數(shù)轉(zhuǎn)換為整數(shù)時,采用該模式硬贯,該模式將在目標數(shù)值類型中選擇一個最接近但是不大于原值的數(shù)字作為最精確的舍入結(jié)果(類似取整)焕襟。

我把所有的算術(shù)指令列一下:

  • 加法指令:iadd、ladd饭豹、fadd鸵赖、dadd
  • 減法指令:isub、lsub拄衰、fsub它褪、dsub
  • 乘法指令:imul、lmul翘悉、fmul茫打、dmul
  • 除法指令:idiv、ldiv妖混、fdiv老赤、ddiv
  • 求余指令:irem、lrem制市、frem抬旺、drem
  • 自增指令:iinc

舉例來說。

public void calculate(int age) {
    int add = age + 1;
    int sub = age - 1;
    int mul = age * 2;
    int div = age / 3;
    int rem = age % 4;
    age++;
    age--;
}

通過 jclasslib 看一下 calculate() 方法的字節(jié)碼指令息堂。

  • iadd嚷狞,加法
  • isub,減法
  • imul荣堰,乘法
  • idiv床未,除法
  • irem,取余
  • iinc振坚,自增的時候 +1薇搁,自減的時候 -1

03、類型轉(zhuǎn)換指令

可以分為兩種:

1)寬化渡八,小類型向大類型轉(zhuǎn)換啃洋,比如 int–>long–>float–>double,對應(yīng)的指令有:i2l屎鳍、i2f宏娄、i2d、l2f逮壁、l2d孵坚、f2d。

  • 從 int 到 long窥淆,或者從 int 到 double卖宠,是不會有精度丟失的;
  • 從 int忧饭、long 到 float扛伍,或者 long 到 double 時,可能會發(fā)生精度丟失词裤;
  • 從 byte刺洒、char 和 short 到 int 的寬化類型轉(zhuǎn)換實際上是隱式發(fā)生的,這樣可以減少字節(jié)碼指令吼砂,畢竟字節(jié)碼指令只有 256 個作媚,占一個字節(jié)。

2)窄化帅刊,大類型向小類型轉(zhuǎn)換纸泡,比如從 int 類型到 byte、short 或者 char赖瞒,對應(yīng)的指令有:i2b女揭、i2s、i2c栏饮;從 long 到 int吧兔,對應(yīng)的指令有:l2i;從 float 到 int 或者 long袍嬉,對應(yīng)的指令有:f2i境蔼、f2l灶平;從 double 到 int、long 或者 float箍土,對應(yīng)的指令有:d2i逢享、d2l、d2f吴藻。

  • 窄化很可能會發(fā)生精度丟失瞒爬,畢竟是不同的數(shù)量級;
  • 但 Java 虛擬機并不會因此拋出運行時異常沟堡。

舉例來說侧但。

public void updown() {
    int i = 10;
    double d = i;
    
    float f = 10f;
    long ong = (long)f;
}

通過 jclasslib 看一下 updown() 方法的字節(jié)碼指令。

  • i2d航罗,int 寬化為 double
  • f2l禀横, float 窄化為 long

04、對象的創(chuàng)建和訪問指令

Java 是一門面向?qū)ο蟮木幊陶Z言粥血,那么 Java 虛擬機是如何從字節(jié)碼層面進行支持的呢燕侠?

1)創(chuàng)建指令

數(shù)組也是一種對象,但它創(chuàng)建的字節(jié)碼指令和普通的對象不同立莉。創(chuàng)建數(shù)組的指令有三種:

  • newarray:創(chuàng)建基本數(shù)據(jù)類型的數(shù)組
  • anewarray:創(chuàng)建引用類型的數(shù)組
  • multianewarray:創(chuàng)建多維數(shù)組

普通對象的創(chuàng)建指令只有一個绢彤,就是 new,它會接收一個操作數(shù)蜓耻,指向常量池中的一個索引茫舶,表示要創(chuàng)建的類型。

舉例來說刹淌。

public void newObject() {
    String name = new String("沉默王二");
    File file = new File("無愁河的浪蕩漢子.book");
    int [] ages = {};
}

通過 jclasslib 看一下 newObject() 方法的字節(jié)碼指令饶氏。

  • new #13 <java/lang/String>,創(chuàng)建一個 String 對象有勾。
  • new #15 <java/io/File>疹启,創(chuàng)建一個 File 對象。
  • newarray 10 (int)蔼卡,創(chuàng)建一個 int 類型的數(shù)組喊崖。

2)字段訪問指令

字段可以分為兩類,一類是成員變量雇逞,一類是靜態(tài)變量(static 關(guān)鍵字修飾的)荤懂,所以字段訪問指令可以分為兩類:

  • 訪問靜態(tài)變量:getstatic、putstatic塘砸。
  • 訪問成員變量:getfield节仿、putfield,需要創(chuàng)建對象后才能訪問掉蔬。

舉例來說廊宪。

public class Writer {
    private String name;
    static String mark = "作者";

    public static void main(String[] args) {
        print(mark);
        Writer w = new Writer();
        print(w.name);
    }

    public static void print(String arg) {
        System.out.println(arg);
    }
}

通過 jclasslib 看一下 main() 方法的字節(jié)碼指令矾瘾。

  • getstatic #2 <com/itwanger/jvm/Writer.mark>,訪問靜態(tài)變量 mark
  • getfield #6 <com/itwanger/jvm/Writer.name>箭启,訪問成員變量 name

05壕翩、方法調(diào)用和返回指令

方法調(diào)用指令有 5 個,分別用于不同的場景:

  • invokevirtual:用于調(diào)用對象的成員方法册烈,根據(jù)對象的實際類型進行分派戈泼,支持多態(tài)婿禽。
  • invokeinterface:用于調(diào)用接口方法赏僧,會在運行時搜索由特定對象實現(xiàn)的接口方法進行調(diào)用。
  • invokespecial:用于調(diào)用一些需要特殊處理的方法扭倾,包括構(gòu)造方法淀零、私有方法和父類方法。
  • invokestatic:用于調(diào)用靜態(tài)方法膛壹。
  • invokedynamic:用于在運行時動態(tài)解析出調(diào)用點限定符所引用的方法驾中,并執(zhí)行。

舉例來說模聋。

public class InvokeExamples {
    private void run() {
        List ls = new ArrayList();
        ls.add("難頂");

        ArrayList als = new ArrayList();
        als.add("學不動了");
    }

    public static void print() {
        System.out.println("invokestatic");
    }

    public static void main(String[] args) {
        print();
        InvokeExamples invoke = new InvokeExamples();
        invoke.run();
    }
}

我們用 javap -c InvokeExamples.class 來反編譯一下肩民。

Compiled from "InvokeExamples.java"
public class com.itwanger.jvm.InvokeExamples {
  public com.itwanger.jvm.InvokeExamples();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  private void run();
    Code:
       0: new           #2                  // class java/util/ArrayList
       3: dup
       4: invokespecial #3                  // Method java/util/ArrayList."<init>":()V
       7: astore_1
       8: aload_1
       9: ldc           #4                  // String 難頂
      11: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      16: pop
      17: new           #2                  // class java/util/ArrayList
      20: dup
      21: invokespecial #3                  // Method java/util/ArrayList."<init>":()V
      24: astore_2
      25: aload_2
      26: ldc           #6                  // String 學不動了
      28: invokevirtual #7                  // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
      31: pop
      32: return

  public static void print();
    Code:
       0: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #9                  // String invokestatic
       5: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return

  public static void main(java.lang.String[]);
    Code:
       0: invokestatic  #11                 // Method print:()V
       3: new           #12                 // class com/itwanger/jvm/InvokeExamples
       6: dup
       7: invokespecial #13                 // Method "<init>":()V
      10: astore_1
      11: aload_1
      12: invokevirtual #14                 // Method run:()V
      15: return
}

InvokeExamples 類有 4 個方法,包括缺省的構(gòu)造方法在內(nèi)链方。

1)InvokeExamples() 構(gòu)造方法中

缺省的構(gòu)造方法內(nèi)部會調(diào)用超類 Object 的初始化構(gòu)造方法:

`invokespecial #1 // Method java/lang/Object."<init>":()V`

2)成員方法 run()

invokeinterface #5,  2  // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z

由于 ls 變量的引用類型為接口 List持痰,所以 ls.add() 調(diào)用的是 invokeinterface 指令,等運行時再確定是不是接口 List 的實現(xiàn)對象 ArrayList 的 add() 方法祟蚀。

invokevirtual #7 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z

由于 als 變量的引用類型已經(jīng)確定為 ArrayList工窍,所以 als.add() 方法調(diào)用的是 invokevirtual 指令。

3)main() 方法中

invokestatic  #11 // Method print:()V

print() 方法是靜態(tài)的前酿,所以調(diào)用的是 invokestatic 指令患雏。

方法返回指令根據(jù)方法的返回值類型進行區(qū)分,常見的返回指令見下圖罢维。

06淹仑、操作數(shù)棧管理指令

常見的操作數(shù)棧管理指令有 pop、dup 和 swap肺孵。

  • 將一個或兩個元素從棧頂彈出攻人,并且直接廢棄,比如 pop悬槽,pop2怀吻;
  • 復制棧頂?shù)囊粋€或兩個數(shù)值并將其重新壓入棧頂,比如 dup初婆,dup2蓬坡,dup_×1猿棉,dup2_×1,dup_×2屑咳,dup2_×2萨赁;
  • 將棧最頂端的兩個槽中的數(shù)值交換位置,比如 swap兆龙。

這些指令不需要指明數(shù)據(jù)類型杖爽,因為是按照位置壓入和彈出的。

舉例來說紫皇。

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

通過 jclasslib 看一下 incAndGet() 方法的字節(jié)碼指令慰安。

  • aload_0:將 this 入棧。
  • dup:復制棧頂?shù)?this聪铺。
  • getfield #2:將常量池中下標為 2 的常量加載到棧上化焕,同時將一個 this 出棧。
  • iconst_1:將常量 1 入棧铃剔。
  • iadd:將棧頂?shù)膬蓚€值相加后出棧撒桨,并將結(jié)果放回棧上。
  • dup_x1:復制棧頂?shù)脑丶担⑵洳迦?this 下面凤类。
  • putfield #2: 將棧頂?shù)膬蓚€元素出棧,并將其賦值給字段 age普气。
  • ireturn:將棧頂?shù)脑爻鰲7祷亍?/li>

07谜疤、控制轉(zhuǎn)移指令

控制轉(zhuǎn)移指令包括:

  • 比較指令,比較棧頂?shù)膬蓚€元素的大小棋电,并將比較結(jié)果入棧茎截。
  • 條件跳轉(zhuǎn)指令,通常和比較指令一塊使用赶盔,在條件跳轉(zhuǎn)指令執(zhí)行前企锌,一般先用比較指令進行棧頂元素的比較,然后進行條件跳轉(zhuǎn)于未。
  • 比較條件轉(zhuǎn)指令撕攒,類似于比較指令和條件跳轉(zhuǎn)指令的結(jié)合體,它將比較和跳轉(zhuǎn)兩個步驟合二為一烘浦。
  • 多條件分支跳轉(zhuǎn)指令抖坪,專為 switch-case 語句設(shè)計的。
  • 無條件跳轉(zhuǎn)指令闷叉,目前主要是 goto 指令擦俐。

1)比較指令

比較指令有:dcmpg,dcmpl、fcmpg榨为、fcmpl、lcmp罕拂,指令的第一個字母代表的含義分別是 double埋合、float备徐、long。注意甚颂,沒有 int 類型蜜猾。

對于 double 和 float 來說,由于 NaN 的存在振诬,有兩個版本的比較指令蹭睡。拿 float 來說,有 fcmpg 和 fcmpl贷揽,區(qū)別在于棠笑,如果遇到 NaN梦碗,fcmpg 會將 1 壓入棧禽绪,fcmpl 會將 -1 壓入棧。

舉例來說洪规。

public void lcmp(long a, long b) {
    if(a > b){}
}

通過 jclasslib 看一下 lcmp() 方法的字節(jié)碼指令印屁。

lcmp 用于兩個 long 型的數(shù)據(jù)進行比較。

2)條件跳轉(zhuǎn)指令

這些指令都會接收兩個字節(jié)的操作數(shù)斩例,它們的統(tǒng)一含義是雄人,彈出棧頂元素,測試它是否滿足某一條件念赶,滿足的話础钠,跳轉(zhuǎn)到對應(yīng)位置。

對于 long叉谜、float 和 double 類型的條件分支比較旗吁,會先執(zhí)行比較指令返回一個整形值到操作數(shù)棧中后再執(zhí)行 int 類型的條件跳轉(zhuǎn)指令。

對于 boolean停局、byte很钓、char、short董栽,以及 int码倦,則直接使用條件跳轉(zhuǎn)指令來完成。

舉例來說锭碳。

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

通過 jclasslib 看一下 fi() 方法的字節(jié)碼指令袁稽。

3 ifne 12 (+9) 的意思是,如果棧頂?shù)脑夭坏扔?0擒抛,跳轉(zhuǎn)到第 12(3+9)行 12 bipush 20推汽。

3)比較條件轉(zhuǎn)指令

前綴“if_”后蝗柔,以字符“i”開頭的指令針對 int 型整數(shù)進行操作,以字符“a”開頭的指令表示對象的比較民泵。

舉例來說癣丧。

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

通過 jclasslib 看一下 compare() 方法的字節(jié)碼指令。

11 if_icmple 18 (+7) 的意思是栈妆,如果棧頂?shù)膬蓚€ int 類型的數(shù)值比較的話胁编,如果前者小于后者時跳轉(zhuǎn)到第 18 行(11+7)。

4)多條件分支跳轉(zhuǎn)指令

主要有 tableswitch 和 lookupswitch鳞尔,前者要求多個條件分支值是連續(xù)的嬉橙,它內(nèi)部只存放起始值和終止值,以及若干個跳轉(zhuǎn)偏移量寥假,通過給定的操作數(shù) index市框,可以立即定位到跳轉(zhuǎn)偏移量位置,因此效率比較高糕韧;后者內(nèi)部存放著各個離散的 case-offset 對枫振,每次執(zhí)行都要搜索全部的 case-offset 對,找到匹配的 case 值萤彩,并根據(jù)對應(yīng)的 offset 計算跳轉(zhuǎn)地址粪滤,因此效率較低。

舉例來說雀扶。

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() 方法的字節(jié)碼指令杖小。

case 2 的時候沒有 break,所以 case 2 和 case 3 是連續(xù)的愚墓,用的是 tableswitch予权。如果等于 1,跳轉(zhuǎn)到 28 行浪册;如果等于 2 和 3扫腺,跳轉(zhuǎn)到 34 行,如果是 default议经,跳轉(zhuǎn)到 40 行斧账。

5)無條件跳轉(zhuǎn)指令

goto 指令接收兩個字節(jié)的操作數(shù),共同組成一個帶符號的整數(shù)煞肾,用于指定指令的偏移量咧织,指令執(zhí)行的目的就是跳轉(zhuǎn)到偏移量給定的位置處。

前面的例子里都出現(xiàn)了 goto 的身影籍救,也很好理解习绢。如果指令的偏移量特別大,超出了兩個字節(jié)的范圍,可以使用指令 goto_w闪萄,接收 4 個字節(jié)的操作數(shù)梧却。


巨人的肩膀:

https://segmentfault.com/a/1190000037628881

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

(騷操作)

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

想要走得更遠圆裕,Java 字節(jié)碼這塊就必須得硬碰硬地吃透广鳍,希望二哥的這些分享可以幫助到大家~

叨逼叨

二哥在 簡書 上寫了很多 Java 方面的系列文章,有 Java 核心語法吓妆、Java 集合框架赊时、Java IO、Java 并發(fā)編程行拢、Java 虛擬機等祖秒,也算是體系完整了。

image

為了能幫助到更多的 Java 初學者舟奠,二哥把自己連載的《教妹學Java》開源到了 GitHub竭缝,盡管只整理了 50 篇,發(fā)現(xiàn)字數(shù)已經(jīng)來到了 10 萬+鸭栖,內(nèi)容更是沒得說歌馍,通俗易懂握巢、風趣幽默晕鹊、圖文并茂

GitHub 開源地址(歡迎 star):https://github.com/itwanger/jmx-java

如果有幫助的話暴浦,還請給二哥點個贊溅话,這將是我繼續(xù)分享下去的最強動力!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末歌焦,一起剝皮案震驚了整個濱河市飞几,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌独撇,老刑警劉巖屑墨,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異纷铣,居然都是意外死亡卵史,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進店門搜立,熙熙樓的掌柜王于貴愁眉苦臉地迎上來以躯,“玉大人,你說我怎么就攤上這事∮巧瑁” “怎么了刁标?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長址晕。 經(jīng)常有香客問我膀懈,道長,這世上最難降的妖魔是什么谨垃? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任吏砂,我火速辦了婚禮,結(jié)果婚禮上乘客,老公的妹妹穿的比我還像新娘狐血。我一直安慰自己,他們只是感情好易核,可當我...
    茶點故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布匈织。 她就那樣靜靜地躺著,像睡著了一般牡直。 火紅的嫁衣襯著肌膚如雪缀匕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天碰逸,我揣著相機與錄音乡小,去河邊找鬼。 笑死饵史,一個胖子當著我的面吹牛满钟,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播胳喷,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼湃番,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了吭露?” 一聲冷哼從身側(cè)響起吠撮,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎讲竿,沒想到半個月后泥兰,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡题禀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年鞋诗,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片投剥。...
    茶點故事閱讀 39,739評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡师脂,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情吃警,我是刑警寧澤糕篇,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站酌心,受9級特大地震影響拌消,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜安券,卻給世界環(huán)境...
    茶點故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一墩崩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧侯勉,春花似錦鹦筹、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至练对,卻和暖如春遍蟋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背螟凭。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工虚青, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人螺男。 一個月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓棒厘,卻偏偏與公主長得像,于是被迫代替她去往敵國和親烟号。 傳聞我的和親對象是個殘疾皇子绊谭,可洞房花燭夜當晚...
    茶點故事閱讀 44,647評論 2 354

推薦閱讀更多精彩內(nèi)容