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.
添加一個字節碼 invokedynamic 。用於提供在缺少靜態類型信息時高效和靈活的執行方法調用。
invokedynamic 是針對 JVM 的,用於更好的支持動態 JVM 語言的函數調用; JSR292 實現則提供在 Java 語言層面上的 invokedynamic 調用。

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

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

API 使用

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

使用基本思路

  1. 建立 MethodType 對象,指定方法的簽名
  2. MethodHandles.Lookup 中查找類型爲 MethodTypeMethodHandle
  3. 傳入方法參數並調用 MethodHandle.invoke 或者 MethodHandle.invokeExact 方法

MethodType

用於指定方法的類型,此處的方法類型與 Java 的方法判斷有點區別,Java 的方法簽名不包括 返回值 ,可是 MethodType 倒是經過 返回值 和 參數類型 來肯定。而且該類型不包含 方法名bootstrap

// 快速建立一個 MethodType 。
MethodType methodType = MethodType.methodType(String.class, Object.class);

// 也能夠經過 MethodHandle 實例獲取它要調用的方法實例
// MethodHandle handle = ...
MethodType type = handle.type();

除了這兩種獲取 MethodType 的方法(實際上第二種不算,MethodHandle 對象的建立依賴於 MethodType)。下面還有兩種,它也是 MethodType 的靜態方法:多線程

  1. genericMethodType: 全部類型都是 Object 類型
  2. fromMethodDescriptorString: 有兩個參數,一個是方法描述符,該描述符是基於 字節碼 層面的描述符。以後就是一個類加載器。

固然,建立完以後還能夠修改返回值類型和參數類型如: changeParameterTypechangeReturnType 等。函數

MethodHandles.Lookup

一個工廠類,用於建立 MethodHandle 類對象。它可經過普通的 findXxx() 方法獲得相應的 MethodHandle 實例,也能夠經過 反射類 來建立實例。如 lookup.unreflect(Method) 等方法。工具

MethodHandle

MethodHandle 可當作是方法引用,它使得 Java 擁有了相似函數指針或委託的方法別名工具。學習

注意幾點:flex

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

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

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

示例ui

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));
}

三種調用方式:.net

  1. invokeExact: 調用此方法與直接調用底層方法同樣,須要作到參數類型精確匹配
  2. invoke: 參數類型鬆散匹配,經過asType自動適配
  3. invokeWithArguments: 直接經過方法參數來調用

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區別
相關文章
相關標籤/搜索