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文件了,如下圖:

Java虛擬機學習:方法調用的字節碼指令


打開命令行,在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
4: return
public java.lang.String getValue(int, int);
Code:
0: aload_0
1: iload_1
2: iload_2
3: invokespecial #2
6: invokestatic #3
9: areturn
public static void output(java.lang.String);
Code:
0: getstatic #4
3: aload_0
4: invokevirtual #5
7: return
public void doAction();
Code:
0: getstatic #4
3: ldc #6
5: invokevirtual #5
8: return
public static void main(java.lang.String[]);
Code:
0: new #7
3: dup
4: invokespecial #8
7: astore_1
8: aload_1
9: astore_2
10: aload_1
11: iconst_1
12: iconst_2
13: invokevirtual #9

16: astore_3
17: aload_1
18: pop
19: aload_3
20: invokestatic #10
23: aload_1
24: invokevirtual #11
27: aload_2
28: invokeinterface #12, 1
33: returnpublic void createThread();
Code:
0: invokedynamic #13, 0
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
6: invokestatic #3
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
6: invokestatic #3
9: areturn

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

invokevirtual:調用實例方法

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

public static void main(java.lang.String[]);
Code:
0: new #7
3: dup
4: invokespecial #8
7: astore_1
8: aload_1
9: astore_2
10: aload_1
11: iconst_1
12: iconst_2
13: invokevirtual #9
16: astore_3
17: aload_1
18: pop
19: aload_3
20: invokestatic #10
23: aload_1
24: invokevirtual #11
27: aload_2
28: invokeinterface #12, 1
33: return}

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

invokeinterface:調用接口方法

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

public static void main(java.lang.String[]);
Code:
0: new #7
3: dup
4: invokespecial #8
7: astore_1
8: aload_1
9: astore_2
10: aload_1
11: iconst_1
12: iconst_2
13: invokevirtual #9
16: astore_3
17: aload_1
18: pop
19: aload_3
20: invokestatic #10
23: aload_1
24: invokevirtual #11
27: aload_2
28: invokeinterface #12, 1
33: return}

可見調用一個接口的方法是通過invokeinterface指令來實現的;

其實t.doAction()和a.doAction()最終都是調用Test001的實例的doAction,但是t的聲明是類,a的聲明是接口,所以兩者的調用指令是不同的;

invokedynamic:調用動態方法

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

0: invokedynamic #13, 0 
5: astore_1
6: return
1

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

Java虛擬機學習:方法調用的字節碼指令


是個Name and type和Bootstrap method,再細看Bootstrap method的操作數,如下圖:

Java虛擬機學習:方法調用的字節碼指令


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

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

關注我:私信回覆“666”獲取往期Java高級架構資料、源碼、筆記、視頻、Dubbo、Redis、Netty、zookeeper、Spring cloud、分佈式、高併發等架構技術


分享到:


相關文章: