JVM方法句柄

JVM方法句柄

方法句柄是一個強類型的,能夠被直接執行的引用。該引用可以指向常規的靜態方法或者實例方法,也可以指向構造器或者字段。當指向字段時,方法句柄實則指向包含字段訪問字節碼的虛構方法,語義上等價於目標字段的 getter 或者 setter 方法

**方法句柄的類型(MethodType)**是由所指向方法的參數類型以及返回類型組成的。它是用來確認方法句柄是否適配的唯一關鍵。當使用方法句柄時,我們其實並不關心方法句柄所指向方法的類名或者方法名

**方法句柄的創建是通過 MethodHandles.Lookup 類來完成的。**它提供了多個 API,既可以使用反射 API 中的 Method 來查找,也可以根據類、方法名以及方法句柄類型來查找

**當使用後者這種查找方式時(方法句柄類型),**用戶需要區分具體的調用類型,比如說對於用 invokestatic 調用的靜態方法,我們需要使用 Lookup.findStatic 方法;對於用 invokevirutal 調用的實例方法,以及用 invokeinterface 調用的接口方法,我們需要使用 findVirtual 方法;對於用 invokespecial 調用的實例方法,我們則需要使用 findSpecial 方法。

具體操作

方法類型

一個 Java 方法可以視爲由四個基本內容所構成:

  • 名稱

  • 簽名(包含返回類型)

  • 定義它的類

  • 實現方法的字節碼

方法句柄首先需要的一個構建塊就是表達方法簽名的方式,以便於查找。在 Java 7 引入的 Method Handles API 中,**這個角色是由 java.lang.invoke.MethodType 類來完成的,它使用一個不可變的實例來代表簽名。要獲取 MethodType,**我們可以使用 methodType() 工廠方法。這是一個參數可變(variadic)的方法,以 class 對象作爲參數。

第一個參數所使用的 class 對象,對應着簽名的返回類型;剩餘參數中所使用的 class 對象,對應着簽名中方法參數的類型。例如:


//toString() 的簽名 
MethodType mtToString = MethodType.methodType(String.class);

// setter 方法的簽名 
MethodType mtSetter = MethodType.methodType(void.class, Object.class);

// Comparator 中 compare() 方法的簽名 
MethodType mtStringComparator = MethodType.methodType(int.class, String.class)

**現在可以使用 MethodType,再組合方法名稱以及定義方法的類來查找方法句柄。**要實現這一點,**我們需要調用靜態的 MethodHandles.lookup() 方法。**這樣的話,會給我們一個“查找上下文(lookup context)”,這個上下文基於當前正在執行的方法(也就是調用 lookup() 的方法)的訪問權限。

**查找上下文對象有一些以“find”開頭的方法,例如,findVirtual()、findConstructor()、findStatic() 等。這些方法將會返回實際的方法句柄,**需要注意的是,只有在創建查找上下文的方法能夠訪問(調用)被請求方法的情況下,纔會返回句柄。這與反射不同,我們沒有辦法繞過訪問控制。換句話說,方法句柄中並沒有與 setAccessible() 對應的方法。

通過MethodHandle進行方法調用一般需要以下幾步:

  • (1)創建MethodType對象,指定方法的簽名;

  • (2)在MethodHandles.Lookup中查找類型爲MethodType的MethodHandle;

  • (3)傳入方法參數並調用MethodHandle.invoke或者MethodHandle.invokeExact方法。

MethodType

可以通過MethodHandle類的type方法查看其類型,返回值是MethodType類的對象。也可以在得到MethodType對象之後,調用MethodHandle.asType(mt)方法適配得到MethodHandle對象。可以通過調用MethodType的靜態方法創建MethodType實例,有三種創建方式:

  • (1)methodType及其重載方法:需要指定返回值類型以及0到多個參數;

  • (2)genericMethodType:需要指定參數的個數,類型都爲Object;

  • (3)fromMethodDescriptorString:通過方法描述來創建。

Lookup

MethodHandle.Lookup相當於MethodHandle工廠類,通過findxxx方法可以得到相應的MethodHandle

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

  • findStatic
    對應字節碼:invokestatic
    調用靜態方法

  • findSpecial
    對應字節碼:invokespecial
    調用實例構造方法,私有方法,父類方法

  • findVirtual
    對應字節碼:invokevirtual
    調用所有的虛方法

  • findVirtual
    對應字節碼:invokeinterface
    調用接口方法,會在運行時再確定一個實現此接口的對象

invoke

在得到MethodHandle後就可以進行方法調用了,有三種調用形式:

  • (1)invokeExact:調用此方法與直接調用底層方法一樣,需要做到參數類型精確匹配;

  • (2)invoke:參數類型鬆散匹配,通過asType自動適配;

  • (3)invokeWithArguments:直接通過方法參數來調用。其實現是先通genericMethodType方法得到MethodType,再通過MethodHandle的asType轉換後得到一個新的MethodHandle,最後通過新MethodHandle的invokeExact方法來完成調用。

官方文檔例子

public class examples {

    public static void main(String[] args) throws Throwable {


        Object x, y; String s; int i;
        MethodType mt; MethodHandle mh;
        MethodHandles.Lookup lookup = MethodHandles.lookup();

// mt is (char,char)String
        mt = MethodType.methodType(String.class, char.class, char.class);
        mh = lookup.findVirtual(String.class, "replace", mt);
        s = (String) mh.invokeExact("daddy",'d','n');


// invokeExact(Ljava/lang/String;CC)Ljava/lang/String;
//        assertEquals(s, "nanny");
        System.out.println(s);
        System.out.println("-----------------------------");



// weakly typed invocation (using MHs.invoke)
        s = (String) mh.invokeWithArguments("sappy", 'p', 'v');
//        assertEquals(s, "savvy");
        System.out.println(s);
        System.out.println("-----------------------------");



// mt is (Object[])List
        mt = MethodType.methodType(java.util.List.class, Object[].class);
        mh = lookup.findStatic(java.util.Arrays.class, "asList", mt);
        assert(mh.isVarargsCollector());
        x = mh.invoke("one", "two");


// invoke(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;
//        assertEquals(x, java.util.Arrays.asList("one","two"));
        System.out.println(x);
        System.out.println("-----------------------------");


// mt is (Object,Object,Object)Object
        mt = MethodType.genericMethodType(3);
        mh = mh.asType(mt);
        x = mh.invokeExact((Object)1, (Object)2, (Object)3);


// invokeExact(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;


//        assertEquals(x, java.util.Arrays.asList(1,2,3));
        System.out.println(x);
        System.out.println("-----------------------------");



// mt is ()int
        mt = MethodType.methodType(int.class);
        mh = lookup.findVirtual(java.util.List.class, "size", mt);
        i = (int) mh.invokeExact(java.util.Arrays.asList(1,2,3));



// invokeExact(Ljava/util/List;)I
        assert(i == 3);
        mt = MethodType.methodType(void.class, String.class);
        mh = lookup.findVirtual(java.io.PrintStream.class, "println", mt);
        mh.invokeExact(System.out, "Hello, world.");


// invokeExact(Ljava/io/PrintStream;Ljava/lang/String;)V

    }
}


MethodHandle 與 Method 區別

  • MethodHandle 在模擬 字節碼 層次的方法調用,因而可適用於所有 JVM 語言 ;

  • Reflection 在模擬 Java 層次的方法調用,僅可適用於 Java。

  • MethodHandle 可進行 JVM 的內聯優化,Reflection 屏蔽了 JVM ,所以完全沒有相應的優化。

  • MethodHandle 從 JVM 層次支持調用,只需要包含方法必要的信息,所以說是輕量級的,而 Reflection 是 Java Api 層次的反射調用,包含了方法的簽名、描述符以及方法屬性表中各種屬性的Java端表示方式,還包含有執行權限等的運行期信息,所以說是重量級的。

  • MethodHandle 方法調用需要考慮到 字節碼,而 Reflection 則不用考慮這些。

參考文章

https://www.infoq.cn/article/Invokedynamic-Javas-secret-weapon

https://segmentfault.com/a/1190000017208820

https://www.zhihu.com/question/40427344/answer/252825611

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章