JSR 292 學習

JSR 292 學習

Add a new bytecode, invokedynamic ,
that supports efficient and flexible execution of method invocations in the absence of static type information.

JSR292 實現於 JDK7,以下爲實現方案的類。

└─java
    └─lang
        ├─invoke
        |   ├─CallSite
        |   ├─ConstantCallSite
        |   ├─MethodHandle
        |   ├─MethodHandleProxies
        |   ├─MethodHandles
        |   ├─MethodHandles.Lookup
        |   ├─MethodType
        |   ├─MutableCallSite
        |   ├─SwitchPoint
        |   └─VolatileCallSite
        ├─ClassValue
        └─BootstrapMethodError

API 使用

JSR292 API 提供了 invokedynamic 字節碼的 API 支持。

invokedynamic

invokedynamic 是本 JSR 爲 JVM 新增的一條方法調用指令。用於更好的支持動態 JVM語言的方法調用。

MethodHandle

MethodHandle 可看成是方法引用,它使得 Java 擁有了類似函數指針或委託的方法別名工具。

注意幾點:

  1. 引用的方法必須和 MethodHandle 的 type 保持一致。
  2. 這裏提到的 type 包括 返回值參數列表
  3. MethodHandle 也是可執行及可以進行轉換。

幾個MethodHandle方法與字節碼的對應:

MethodHandle方法 字節碼 描述
findStatic invokestatic 調用靜態方法
findSpecial invokespecial 調用實例構造方法,私有方法,父類方法。
findVirtual invokevirtual 調用所有的虛方法
findVirtual invokeinterface 調用接口方法,會在運行時再確定一個實現此接口的對象。

示例

public void example() throws Throwable {
    // MethodHandles.Lookup 類似於 MethodHandle 工廠類,用於創建 MethodHandle
    MethodHandles.Lookup lookup = MethodHandles.lookup();
    MethodHandle mh = lookup.findStatic(String.class, "valueOf", MethodType.methodType(String.class, int.class));

    String result = (String) mh.invoke(1);
    System.out.println("1".equals(result));
}

CallSite

當 JVM 執行 invokedynamic 指令時,首先需要鏈接其對應的 _動態調用點_。
在鏈接的時候,JVM會先調用一個啓動方法(bootstrap method)。
這個啓動方法的返回值是 java.lang.invoke.CallSite 類的對象。

在通過啓動方法得到了 CallSite 之後,通過這個 CallSite 對象的 getTarget() 可以獲取到實際要調用的目標方法句柄。
有了方法句柄之後,對這個 動態調用點 的調用,實際上是代理給方法句柄來完成的。
也就是說,對 invokedynamic 指令的調用實際上就等價於對方法句柄的調用,具體來說是被轉換成對方法句柄的invoke方法的調用。

JDK7 中提供了三種類型的動態調用點CallSite的實現:java.lang.invoke.ConstantCallSitejava.lang.invoke.MutableCallSitejava.lang.invoke.VolatileCallSite

ConstantCallSite

表示的調用點綁定的是一個固定的方法句柄,一旦鏈接之後,就無法修改。示例如下:

public void constantCallSite() throws Throwable {
    MethodType type = MethodType.methodType(String.class, int.class, int.class);

    MethodHandles.Lookup lookup = MethodHandles.lookup();
    MethodHandle handle = lookup.findVirtual(String.class, "substring", type);

    ConstantCallSite callSite = new ConstantCallSite(handle);
    MethodHandle invoker = callSite.dynamicInvoker();
    String result = (String) invoker.invoke("Hello", 2, 3);
    System.out.println(result);
}

MutableCallSite

表示的調用點則允許在運行時動態修改其目標方法句柄,即可以重新鏈接到新的方法句柄上。示例如下:

/**
 * MutableCallSite 允許對其所關聯的目標方法句柄通過setTarget方法來進行修改。
 * 以下爲 創建一個 MutableCallSite,指定了方法句柄的類型,則設置的其他方法也必須是這種類型。
 */
public void useMutableCallSite() throws Throwable {
    MethodType type = MethodType.methodType(int.class, int.class, int.class);
    MutableCallSite callSite = new MutableCallSite(type);
    MethodHandle invoker = callSite.dynamicInvoker();

    MethodHandles.Lookup lookup = MethodHandles.lookup();
    MethodHandle maxHandle = lookup.findStatic(Math.class, "max", type);
    callSite.setTarget(maxHandle);
    int result = (int) invoker.invoke(3, 5);
    System.out.println(result == 5);

    MethodHandle minHandle = lookup.findStatic(Math.class, "min", type);
    callSite.setTarget(minHandle);
    result = (int) invoker.invoke(3, 5);
    System.out.println(result == 3);
}

MutableCallSite.syncAll() 提供了方法來強制要求各個線程中 MutableCallSite 的使用者立即獲取最新的目標方法句柄。
但這個時候也可以選擇使用 VolatileCallSite

VolatileCallSite

作用與 MutableCallSite 類似,不同的是它適用於多線程情況,用來保證對於目標方法句柄所做的修改能夠被其他線程看到。
此處便不再提供示例,可參考 MutableCallSite

MethodHandle 與 Method 區別

  1. MethodHandle 在模擬 字節碼 層次的方法調用,因而可適用於所有 _JVM 語言_;Reflection 在模擬 Java 層次的方法調用,僅可適用於 Java。
  2. MethodHandle 可進行 JVM 的內聯優化,Reflection 屏蔽了 JVM ,所以完全沒有相應的優化。
  3. MethodHandle 從 JVM 層次支持調用,只需要包含方法必要的信息,所以說是輕量級的,而 Reflection 是 Java Api 層次的反射調用,包含了方法的簽名、描述符以及方法屬性表中各種屬性的Java端表示方式,還包含有執行權限等的運行期信息,所以說是重量級的。
  4. MethodHandle 方法調用需要考慮到 字節碼,而 Reflection 則不用考慮這些。

參考

  1. JDK1.8下關於MethodHandle問題
  2. Invokedynamic 和 MethodHandle的緣由
  3. MethodHandle與反射Method區別
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章