天天看點

我要悄悄學習 Java 位元組碼指令,在成為技術大佬的路上一去不複返(3)

04、對象的建立和通路指令

Java 是一門面向對象的程式設計語言,那麼 Java 虛拟機是如何從位元組碼層面進行支援的呢?

1)建立指令

數組也是一種對象,但它建立的位元組碼指令和普通的對象不同。建立數組的指令有三種:

newarray:建立基本資料類型的數組

anewarray:建立引用類型的數組

multianewarray:建立多元數組

普通對象的建立指令隻有一個,就是 new,它會接收一個操作數,指向常量池中的一個索引,表示要建立的類型。

舉例來說。

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

通過 jclasslib 看一下 newObject() 方法的位元組碼指令。

我要悄悄學習 Java 位元組碼指令,在成為技術大佬的路上一去不複返(3)

new #13 <java/lang/String>,建立一個 String 對象。

new #15 <java/io/File>,建立一個 File 對象。

newarray 10 (int),建立一個 int 類型的數組。

2)字段通路指令

字段可以分為兩類,一類是成員變量,一類是靜态變量(static 關鍵字修飾的),是以字段通路指令可以分為兩類:

通路靜态變量:getstatic、putstatic。

通路成員變量:getfield、putfield,需要建立對象後才能通路。

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() 方法的位元組碼指令。

我要悄悄學習 Java 位元組碼指令,在成為技術大佬的路上一去不複返(3)

getstatic #2 <com/itwanger/jvm/Writer.mark>,通路靜态變量 mark

getfield #6 <com/itwanger/jvm/Writer.name>,通路成員變量 name

05、方法調用和傳回指令

方法調用指令有 5 個,分别用于不同的場景:

invokevirtual:用于調用對象的成員方法,根據對象的實際類型進行分派,支援多态。

invokeinterface:用于調用接口方法,會在運作時搜尋由特定對象實作的接口方法進行調用。

invokespecial:用于調用一些需要特殊處理的方法,包括構造方法、私有方法和父類方法。

invokestatic:用于調用靜态方法。

invokedynamic:用于在運作時動态解析出調用點限定符所引用的方法,并執行。

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 個方法,包括預設的構造方法在内。

1)InvokeExamples() 構造方法中

預設的構造方法内部會調用超類 Object 的初始化構造方法:

`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() 調用的是 invokeinterface 指令,等運作時再确定是不是接口 List 的實作對象 ArrayList 的 add() 方法。

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

由于 als 變量的引用類型已經确定為 ArrayList,是以 als.add() 方法調用的是 invokevirtual 指令。

3)main() 方法中

invokestatic  #11 // Method print:()V

print() 方法是靜态的,是以調用的是 invokestatic 指令。

方法傳回指令根據方法的傳回值類型進行區分,常見的傳回指令見下圖。

我要悄悄學習 Java 位元組碼指令,在成為技術大佬的路上一去不複返(3)