08 | JVM是怎么实现invokedynamic的?(上)
郑雨迪
该思维导图由 AI 生成,仅供参考
前不久,“虚拟机”赛马俱乐部来了个年轻人,标榜自己是动态语言,是先进分子。
这一天,先进分子牵着一头鹿进来,说要参加赛马。咱部里的老学究 Java 就不同意了呀,鹿又不是马,哪能参加赛马。
当然了,这种墨守成规的调用方式,自然是先进分子所不齿的。现在年轻人里流行的是鸭子类型(duck typing)[1],只要是跑起来像只马的,它就是一只马,也就能够参加赛马比赛。
(如何用同一种方式调用他们的赛跑方法?)
说到了这里,如果我们将赛跑定义为对赛跑方法(对应上述代码中的 race())的调用的话,那么这个故事的关键,就在于能不能在马场中调用非马类型的赛跑方法。
为了解答这个问题,我们先来回顾一下 Java 里的方法调用。在 Java 中,方法调用会被编译为 invokestatic,invokespecial,invokevirtual 以及 invokeinterface 四种指令。这些指令与包含目标方法类名、方法名以及方法描述符的符号引用捆绑。在实际运行之前,Java 虚拟机将根据这个符号引用链接到具体的目标方法。
可以看到,在这四种调用指令中,Java 虚拟机明确要求方法调用需要提供目标方法的类名。在这种体系下,我们有两个解决方案。一是调用其中一种类型的赛跑方法,比如说马类的赛跑方法。对于非马的类型,则给它套一层马甲,当成马来赛跑。
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
- 深入了解
- 翻译
- 解释
- 总结
Java 7引入了invokedynamic指令和方法句柄(MethodHandle),以解决传统方法调用的限制。invokedynamic指令允许灵活的方法调用,而方法句柄是一个强类型的、能够直接执行的引用,可以指向静态方法、实例方法、构造器或字段。方法句柄的类型由所指向方法的参数类型和返回类型组成,而方法句柄的创建是通过MethodHandles.Lookup类来完成的。方法句柄的调用可分为严格匹配参数类型的invokeExact和自动适配参数类型的invoke,同时还支持增删改参数的操作。虽然方法句柄的访问权限不取决于方法句柄的创建位置,而是取决于Lookup对象的创建位置,但应用程序需要负责方法句柄的管理。文章还介绍了方法句柄调用的具体实现,包括invokeExact调用的特殊处理和方法句柄的内联瓶颈。总的来说,invokedynamic和方法句柄的引入使得Java在方法调用方面变得更加灵活和高效。
仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《深入拆解 Java 虚拟机》,新⼈⾸单¥59
《深入拆解 Java 虚拟机》,新⼈⾸单¥59
立即购买
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
登录 后留言
全部留言(38)
- 最新
- 精选
- Geek_488a8e这篇读的好吃力,我的一个建议,先抛出一个使用方法句柄的代码例子,然后再剖析代码在虚拟机的实际过程。自顶向下的讲,由浅入深,这篇直接自底向上了,咬咬牙读到最后,才发现这是类似反射的模拟方法调用的方法
作者回复: 多谢建议!
2018-08-30242 - ext4雨迪,我看了一下MethodHandle的增操作,即你所提到的bindTo这个API,它貌似只能用于为virtual method绑定第一个参数(即caller的this*指针),并不能普适地为方法绑定一个任意参数(例如把参数列表(int, int)里的第一个参数绑定为常数4)。那么你例子中所提到的更为一般性的柯里化又是怎么实现的呢?
作者回复: 多谢指出 :) bindTo确实限制了只能使用引用类型,而且正如你所说普遍是用来绑定this的。但是由于方法句柄不区分调用者和参数,所以还是可以滥用的。 你可以试试用Integer,然后使用静态方法,或者在使用virtual方法时将bindTo返回的方法句柄再bindTo一个Integer.valueOf(4) BoundMethodHande里有各种非公有的bindArgument*方法,感兴趣可以了解一下
2018-08-095 - 秋天这个东西的应用场景是什么?讲的挺深,联系不起来知识2018-08-13495
- Yoph方法句柄VS反射VS代理: 从访问控制层面来讲,反射需要调用setAccesible(),可能会受到安全管理器的禁止警告;代理有些情况下通过内部类实现,但是内部类只能访问受限的函数或字段;而方法句柄则在上下文中对所有方法都有完整的访问权限,并且不会受到安全管理器的限制,这是方法句柄的优势之一。 从执行速度层面来讲,在上一篇中老师也讲到了反射的性能会受到参数方法、类型的自动装箱和拆箱、方法内联的影响,相对来讲反射算是执行较慢的了(当然并没有和方法句柄通过执行具体操作示例作对比,可能在不同的JVM配置情况下执行情况不一样,比如解释器模式或编译模式下等);通过代理的方式因调用JAVA函数实现,速度与其它调用函数的速度是一样的,相对较快;而方法句柄可能不会有代理方式那样的执行速度快,但同样会受到JVM等不同的配置导致速度不同,但从JVM设计者的角度来说,应该是力求达到像调用函数一样快的速度,目前可能是达不到的。 从类的开销层面来讲,代理通常声明多个类,需要占用方法区,而方法句柄并不需要像代理一样有多个类的开销,不需要方法区的开销。2018-11-13140
- 飞翔明天老师说后面会简单一点,但是我到这篇,感觉好难,看不懂了2019-07-04210
- 李二木那么方法句柄是否可以取代反射了呢?2018-08-089
- 雨亦奇个人觉得还是按老师的课程安排来走吧。跳来跳去的讲可能会零散不系统。上面两位的说法我不赞同。2018-08-097
- lornsoul1.将句柄设为常量; 2.将new Foo() 和 new Object() 提到循环体外; 3.参数设置 ‘-Djava.lang.invoke.MethodHandle.CUSTOMIZE_THRESHOLD=1’ (好像作用不大) ‘将方法句柄变成常量’获得的性能提升是怎么来的呢?JVM做了什么优化了吗? public class Foo { public void bar(Object o) { } static final MethodHandle mh; static { MethodHandles.Lookup l = MethodHandles.lookup(); MethodType t = MethodType.methodType(void.class, Object.class); MethodHandle mh0 = null; try { mh0 = l.findVirtual(Foo.class, "bar", t); } catch (NoSuchMethodException | IllegalAccessException e) { e.printStackTrace(); } finally { mh = mh0; } } public static void main(String[] args) throws Throwable { Foo foo = new Foo(); Object o = new Object(); long current = System.currentTimeMillis(); for (int i = 1; i <= 2_000_000_000; i++) { if (i % 100_000_000 == 0) { long temp = System.currentTimeMillis(); System.out.println(temp - current); current = temp; } mh.invokeExact(foo, o); } } }2020-11-255
- Yoph方法句柄VS反射VS代理: 从访问控制层面来讲,反射需要调用setAccesible(),可能会受到安全管理器的禁止警告;代理有些情况下通过内部类实现,但是内部类只能访问受限的函数或字段;而方法句柄则在上下文中对所有方法都有完整的访问权限,并且不会受到安全管理器的限制,这是方法句柄的优势之一。 从执行速度层面来讲,在上一篇中老师也讲到了反射的性能会受到参数方法、类型的自动装箱和拆箱、方法内联的影响,相对来讲反射算是执行较慢的了(当然并没有和方法句柄通过执行具体操作示例作对比,可能在不同的JVM配置情况下执行情况不一样,比如解释器模式或编译模式下等);通过代理的方式因调用JAVA函数实现,速度与其它调用函数的速度是一样的,相对较快;而方法句柄可能不会有代理方式那样的执行速度快,但同样会受到JVM等不同的配置导致速度不同,但从JVM设计者的角度来说,应该是力求达到像调用函数一样快的速度,目前可能是达不到的。 从类的开销层面来讲,代理通常声明多个类,需要占用方法区,而方法句柄并不需要像代理一样有多个类的开销,不需要方法区的开销。2018-11-135
- Yoph方法句柄其实就是可以取得与反射相同的效果,不过方法句柄使用的代码更简洁。使用方法句柄,可以去掉反射中很多套路化的代码,提高代码的可读性。2018-11-1314
收起评论