簡體   English   中英

Java 字節碼:在具有不同類的對象引用上調用虛擬方法引用

[英]Java Bytecode: invokevirtual methodref on an objectref with a different class

我目前正在研究 Java 字節碼的工作原理。 我創建了這個簡單的測試類:

class Main {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

使用javap -c Main.class我可以獲得它的字節碼:

class Main {
  Main();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #13                 // String Hello, World!
       5: invokevirtual #15                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return
}

應該執行的第一條指令(根據我的理解)應該是main函數中的getstatic指令。 這會導致加載System類和其他類似Console的類。

Console<clinit>方法期間:

static {};
    Code:
       0: ldc           #2                  // class java/io/Console
       2: invokevirtual #203                // Method java/lang/Class.desiredAssertionStatus:()Z
...

執行invokevirtual方法以調用類Class上的desiredAssertionStatus函數。 已經可以看出此invokevirtual指令與上述指令之間的區別: javap將類名附加在方法名之前,因為該函數是在類Class中找到的,而不是在Console中。

所以最后我的問題是:這里發生了什么? 根據JVM 19 的invokevirtual規范,該方法應該在堆棧上的對象上調用。 在這種情況下, Console但此類沒有請求的方法,也不是 Class 的子Class JVM 打算在這里做什么/像 Hotspot 這樣的 JVM 實現是如何做到這一點的。 他們只是在指令期間將方法放到Console類中,還是以某種方式將類Class注入到Console中,或者這里可能發生了一些我遺漏的完全不同的事情?

無論如何,感謝您花時間閱讀我的問題!

我希望你有一個美好的一天:)

我想過做以下事情:

  • 在指令期間將方法添加到Console類。 這不起作用,因為該方法需要來自類Class的字段。

  • 實際上只是在動態創建的Class實例上調用該方法,但我認為這些看起來很奇怪。

  • 這可能是一個非常特殊的情況,因為我認為它與Console試圖與其ClassLoader交互有關。 如果這是一個特殊情況並且這種情況發生的次數不多:也許 JVM 只是做了一些幕后魔術,比如在加載后為每個類分配一個Class實例,然后僅用於此目的。 對我來說也很奇怪。

你寫了

已經可以看出此invokevirtual指令與上述指令之間的區別: javap將類名附加在方法名之前,因為該函數是在類Class中找到的,而不是在Console中。

但在這方面沒有區別。 javap在這兩種情況下都包含了類名。 對於println方法,它是類java/io/PrintStream

對於普通的 Java 代碼, ldc指令可以加載原始值或StringClass類型的對象。 我們可以用

public class Main {
  static {
        Console.class.desiredAssertionStatus();
        "hello".toString();
    }
  
    public static void main(String[] args) {
        showBytecode();
    }
  
    private static void showBytecode() {
        ToolProvider.findFirst("javap")
            .ifPresent(p -> p.run(System.out, System.err, "-c", "Main"));
    }
  
    private Main() {}
}
在線示例
Compiled from "Main.java"
public class Main {
  public static void main(java.lang.String[]);
    Code:
       0: invokestatic  #1                  // Method showBytecode:()V
       3: return

  static {};
    Code:
       0: ldc           #13                 // class java/io/Console
       2: invokevirtual #14                 // Method java/lang/Class.desiredAssertionStatus:()Z
       5: pop
       6: ldc           #15                 // String hello
       8: invokevirtual #16                 // Method java/lang/String.toString:()Ljava/lang/String;
      11: pop
      12: return
}

我們可以看到javap總是在ldc指令之后打印實際值,所以在這個例子中,“class java/io/Console”和“String hello”。

ldc指令還可以加載其他類型的對象,但沒有與這些用例等效的普通 Java 語言。

desiredAssertionStatus()查詢通常用於實現assert語句支持,例如。

public class Main {
    public static void main(String[] args) {
        assert "foo".length() == 3;
        showBytecode();
    }

    private static void showBytecode() {
        ToolProvider.findFirst("javap")
            .ifPresent(p -> p.run(System.out, System.err, "-c", "Main"));
    }

    private Main() {}
}
被編譯為
Compiled from "Main.java"
public class Main {
  static final boolean $assertionsDisabled;

  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #1                  // Field $assertionsDisabled:Z
       3: ifne          23
       6: ldc           #2                  // String foo
       8: invokevirtual #3                  // Method java/lang/String.length:()I
      11: iconst_3
      12: if_icmpeq     23
      15: new           #4                  // class java/lang/AssertionError
      18: dup
      19: invokespecial #5                  // Method java/lang/AssertionError."<init>":()V
      22: athrow
      23: invokestatic  #6                  // Method showBytecode:()V
      26: return

  static {};
    Code:
       0: ldc           #18                 // class Main
       2: invokevirtual #19                 // Method java/lang/Class.desiredAssertionStatus:()Z
       5: ifne          12
       8: iconst_1
       9: goto          13
      12: iconst_0
      13: putstatic     #1                  // Field $assertionsDisabled:Z
      16: return
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM