Java1.8環境下,咱們在編寫程序時會進行各類方法調用,虛擬機在執行這些調用的時候會用到不一樣的字節碼指令,共有以下五種:java
這裏咱們經過一個實例將這些方法調用的字節碼指令逐個列出。程序員
實例共兩個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的代碼中主要的方法以下:學習
接下來咱們經過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 }
如今咱們能夠對比反彙編結果來學習字節碼的用法了:對象
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指令來實現的;接口
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指令來實現的;
在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指令來實現的;
在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的聲明是接口,因此二者的調用指令是不一樣的;
在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方法;
以上就是五種方法調用的字節碼指令的簡單介紹,實際上每一個指令背後都對應着更復雜的調用和操做,有興趣的讀者能夠經過虛擬機相關的書籍和資料繼續深刻學習。