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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353

推荐阅读更多精彩内容