Java方法調用的字節碼指令學習

Java1.8環境下,咱們在編寫程序時會進行各類方法調用,虛擬機在執行這些調用的時候會用到不一樣的字節碼指令,共有以下五種:java

  1. invokespecial:調用私有實例方法;
  2. invokestatic:調用靜態方法;
  3. invokevirtual:調用實例方法;
  4. invokeinterface:調用接口方法;
  5. invokedynamic:調用動態方法;

這裏咱們經過一個實例將這些方法調用的字節碼指令逐個列出。程序員

實例源碼

實例共兩個java文件,一個是接口另外一個是類,先看接口源碼,很簡單隻有一個方法聲明:ide

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目錄下執行<font color="blue">javap -c Test001.class </font>,就能夠對class文件進行反彙編,獲得結果以下:code

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的那一行:blog

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方法;

以上就是五種方法調用的字節碼指令的簡單介紹,實際上每一個指令背後都對應着更復雜的調用和操做,有興趣的讀者能夠經過虛擬機相關的書籍和資料繼續深刻學習。

歡迎關注個人公衆號:程序員欣宸

相關文章
相關標籤/搜索