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 擁有了類似函數指針或委託的方法別名工具。
注意幾點:
- 引用的方法必須和 MethodHandle 的 type 保持一致。
- 這裏提到的 type 包括 返回值 和 參數列表 。
- 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.ConstantCallSite
、java.lang.invoke.MutableCallSite
和 java.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 區別
- MethodHandle 在模擬 字節碼 層次的方法調用,因而可適用於所有 _JVM 語言_;Reflection 在模擬 Java 層次的方法調用,僅可適用於 Java。
- MethodHandle 可進行 JVM 的內聯優化,Reflection 屏蔽了 JVM ,所以完全沒有相應的優化。
- MethodHandle 從 JVM 層次支持調用,只需要包含方法必要的信息,所以說是輕量級的,而 Reflection 是 Java Api 層次的反射調用,包含了方法的簽名、描述符以及方法屬性表中各種屬性的Java端表示方式,還包含有執行權限等的運行期信息,所以說是重量級的。
- MethodHandle 方法調用需要考慮到
字節碼
,而 Reflection 則不用考慮這些。