1. 程式人生 > >Java虛擬機器學習:方法呼叫的位元組碼指令

Java虛擬機器學習:方法呼叫的位元組碼指令

我們在寫java程式的時候會進行各種方法呼叫,虛擬機器在執行這些呼叫的時候會用到不同的位元組碼指令,共有如下五種:
1. invokespecial:呼叫私有例項方法;
2. invokestatic:呼叫靜態方法;
3. invokevirtual:呼叫例項方法;
4. invokeinterface:呼叫介面方法;
5. invokedynamic:呼叫動態方法;

這裡我們通過一個例項將這些方法呼叫的位元組碼指令逐個列出。

例項原始碼

例項共兩個java檔案,一個是介面另一個是類,先看介面原始碼,很簡單隻有一個方法宣告:

package com.bolingcavalry;

public
interface Action { void doAction(); }

接下來的類實現了這個介面,而且還有自己的共有、私有、靜態方法:

package com.bolingcavalry;

public class Test001 implements Action{
    private int add(int a, int b){
        return a+b;
    }

    public String getValue(int a, int b){
        return String.valueOf(add(a,b));
    }

    public
static void output(String str){ System.out.println(str); } @Override public void doAction() { System.out.println("123"); } public static void main(String[] args){ Test001 t = new Test001(); Action a = t; String str = t.getValue(1,2); t.output(str); t.doAction(); a.doAction(); } public
void createThread(){ Runnable r = () -> System.out.println("123"); } }

小結一下,Test001的程式碼中主要的方法如下:
1. 一個私有方法add;
2. 一個公有方法getValue,裡面呼叫了add方法;
3. 一個靜態方法output;
4. 實現介面定義的doAction;
5. 一個公有方法,裡面使用了lambda表示式;
6. main方法中,建立物件,呼叫getValue,output,doAction;

接下來我們通過javac命令或者ide工具得到Action.class和Test001.class檔案,如果是用intellij idea,可以先把Test001執行一遍,然後在工程目錄下找到out資料夾,開啟后里面是production資料夾,再進去就能找到對應的package和class檔案了,如下圖:

這裡寫圖片描述

開啟命令列,在Test001.class目錄下執行javap -c Test001.class
,就可以對class檔案進行反彙編,得到結果如下:

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

  public java.lang.String getValue(int, int);
    Code:
       0: aload_0
       1: iload_1
       2: iload_2
       3: invokespecial #2                  // Method add:(II)I
       6: invokestatic  #3                  // Method java/lang/String.valueOf:(I)Ljava/lang/String;
       9: areturn

  public static void output(java.lang.String);
    Code:
       0: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: aload_0
       4: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       7: return

  public void doAction();
    Code:
       0: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #6                  // String 123
       5: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #7                  // class com/bolingcavalry/Test001
       3: dup
       4: invokespecial #8                  // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: astore_2
      10: aload_1
      11: iconst_1
      12: iconst_2
      13: invokevirtual #9                  // Method getValue:(II)Ljava/lang/String;
      16: astore_3
      17: aload_1
      18: pop
      19: aload_3
      20: invokestatic  #10                 // Method output:(Ljava/lang/String;)V
      23: aload_1
      24: invokevirtual #11                 // Method doAction:()V
      27: aload_2
      28: invokeinterface #12,  1           // InterfaceMethod com/bolingcavalry/Action.doAction:()V
      33: return

public void createThread();
    Code:
       0: invokedynamic #13,  0             // InvokeDynamic #0:run:()Ljava/lang/Runnable;
       5: astore_1
       6: return

}

現在我們可以對比反彙編結果來學習位元組碼的用法了:

invokespecial:呼叫私有例項方法

getValue()方法中呼叫了私有例項方法add(int a, int b),反編譯結果如下所示,注意編號為3的那一行:

public java.lang.String getValue(int, int);
    Code:
       0: aload_0
       1: iload_1
       2: iload_2
       3: invokespecial #2                  // Method add:(II)I
       6: invokestatic  #3                  // Method java/lang/String.valueOf:(I)Ljava/lang/String;
       9: areturn

可見私有例項方法的呼叫是通過invokespecial指令來實現的;

invokestatic:呼叫靜態方法

getValue()方法中,呼叫了靜態方法String.valueOf(),反編譯結果如下所示,注意編號為6的那一行:

public java.lang.String getValue(int, int);
    Code:
       0: aload_0
       1: iload_1
       2: iload_2
       3: invokespecial #2                  // Method add:(II)I
       6: invokestatic  #3                  // Method java/lang/String.valueOf:(I)Ljava/lang/String;
       9: areturn

可見靜態方法的呼叫是通過invokestatic指令來實現的;

invokevirtual:呼叫例項方法

在main()方法中,呼叫了t.getValue(1,2)方法,反編譯結果如下所示,注意編號為13的那一行:

public static void main(java.lang.String[]);
    Code:
       0: new           #7                  // class com/bolingcavalry/Test001
       3: dup
       4: invokespecial #8                  // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: astore_2
      10: aload_1
      11: iconst_1
      12: iconst_2
      13: invokevirtual #9                  // Method getValue:(II)Ljava/lang/String;
      16: astore_3
      17: aload_1
      18: pop
      19: aload_3
      20: invokestatic  #10                 // Method output:(Ljava/lang/String;)V
      23: aload_1
      24: invokevirtual #11                 // Method doAction:()V
      27: aload_2
      28: invokeinterface #12,  1           // InterfaceMethod com/bolingcavalry/Action.doAction:()V
      33: return
}

可見呼叫一個例項的方法的時候,通過invokevirtual指令來實現的;

invokeinterface:呼叫介面方法

在main()方法中,我們聲明瞭介面Action a,然後呼叫了a.doAction(),反編譯結果如下所示,注意編號為28的那一行:

public static void main(java.lang.String[]);
    Code:
       0: new           #7                  // class com/bolingcavalry/Test001
       3: dup
       4: invokespecial #8                  // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: astore_2
      10: aload_1
      11: iconst_1
      12: iconst_2
      13: invokevirtual #9                  // Method getValue:(II)Ljava/lang/String;
      16: astore_3
      17: aload_1
      18: pop
      19: aload_3
      20: invokestatic  #10                 // Method output:(Ljava/lang/String;)V
      23: aload_1
      24: invokevirtual #11                 // Method doAction:()V
      27: aload_2
      28: invokeinterface #12,  1           // InterfaceMethod com/bolingcavalry/Action.doAction:()V
      33: return
}

可見呼叫一個介面的方法是通過invokeinterface指令來實現的;
其實t.doAction()和a.doAction()最終都是呼叫Test001的例項的doAction,但是t的宣告是類,a的宣告是介面,所以兩者的呼叫指令是不同的;

invokedynamic:呼叫動態方法

在main()方法中,我們聲明瞭一個lambda() -> System.out.println(“123”),反編譯的結果如下:

 0: invokedynamic #13,  0             // InvokeDynamic #0:run:()Ljava/lang/Runnable;
       5: astore_1
       6: return

可見lambda表示式對應的實際上是一個invokedynamic呼叫,具體的呼叫內容,可以用Bytecode viewer這個工具來開啟Test001.class再研究,由於反編譯後得到invokedynamic的運算元是#13,我們先去常量池看看13對應的內容:

這裡寫圖片描述

是個Name and type和Bootstrap method,再細看Bootstrap method的運算元,如下圖:

這裡寫圖片描述

是個MethodHandler的引用,指向了使用者實現的lambda方法;

以上就是五種方法呼叫的位元組碼指令的簡單介紹,實際上每個指令背後都對應著更復雜的呼叫和操作,有興趣的讀者可以通過虛擬機器相關的書籍和資料繼續深入學習。