深入拆解 Java 虚拟机
郑雨迪
Oracle 高级研究员,计算机博士
87446 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 40 讲
模块四:黑科技 (3讲)
深入拆解 Java 虚拟机
15
15
1.0x
00:00/00:00
登录|注册

08 | JVM是怎么实现invokedynamic的?(上)

linkToStatic
适配器
invokeBasic
Invokers.checkCustomized
Invokers.checkExactType
linkToStatic
LambdaForm
适配器
特殊适配器
invokeBasic调用
invokeExact调用
用于实现方法的柯里化
MethodHandle.bindTo
MethodHandles.dropArguments
MethodHandle.asType
签名多态性
签名多态性
严格匹配参数类型
重构代码,将方法句柄变成常量
内联瓶颈
DirectMethodHandle
增删改参数的操作
invoke
invokeExact
应用程序需要负责方法句柄的管理
访问权限取决于Lookup对象的创建位置
权限检查在句柄的创建阶段完成
区分具体的调用类型
MethodHandles.Lookup类提供API
方法句柄类型(MethodType)确认方法句柄是否适配的唯一关键
指向静态方法、实例方法、构造器或字段
强类型的、直接执行的引用
测量方法句柄的性能
性能
实现
操作
权限问题
创建
概念
总结与实践
方法句柄(MethodHandle)
JVM实现invokedynamic
JVM实现invokedynamic的知识关系脑图

该思维导图由 AI 生成,仅供参考

前不久,“虚拟机”赛马俱乐部来了个年轻人,标榜自己是动态语言,是先进分子。
这一天,先进分子牵着一头鹿进来,说要参加赛马。咱部里的老学究 Java 就不同意了呀,鹿又不是马,哪能参加赛马。
当然了,这种墨守成规的调用方式,自然是先进分子所不齿的。现在年轻人里流行的是鸭子类型(duck typing)[1],只要是跑起来像只马的,它就是一只马,也就能够参加赛马比赛。
class Horse {
public void race() {
System.out.println("Horse.race()");
}
}
class Deer {
public void race() {
System.out.println("Deer.race()");
}
}
class Cobra {
public void race() {
System.out.println("How do you turn this on?");
}
}
(如何用同一种方式调用他们的赛跑方法?)
说到了这里,如果我们将赛跑定义为对赛跑方法(对应上述代码中的 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
立即购买
登录 后留言

全部留言(38)

  • 最新
  • 精选
  • Geek_488a8e
    这篇读的好吃力,我的一个建议,先抛出一个使用方法句柄的代码例子,然后再剖析代码在虚拟机的实际过程。自顶向下的讲,由浅入深,这篇直接自底向上了,咬咬牙读到最后,才发现这是类似反射的模拟方法调用的方法

    作者回复: 多谢建议!

    2018-08-30
    2
    42
  • ext4
    雨迪,我看了一下MethodHandle的增操作,即你所提到的bindTo这个API,它貌似只能用于为virtual method绑定第一个参数(即caller的this*指针),并不能普适地为方法绑定一个任意参数(例如把参数列表(int, int)里的第一个参数绑定为常数4)。那么你例子中所提到的更为一般性的柯里化又是怎么实现的呢?

    作者回复: 多谢指出 :) bindTo确实限制了只能使用引用类型,而且正如你所说普遍是用来绑定this的。但是由于方法句柄不区分调用者和参数,所以还是可以滥用的。 你可以试试用Integer,然后使用静态方法,或者在使用virtual方法时将bindTo返回的方法句柄再bindTo一个Integer.valueOf(4) BoundMethodHande里有各种非公有的bindArgument*方法,感兴趣可以了解一下

    2018-08-09
    5
  • 秋天
    这个东西的应用场景是什么?讲的挺深,联系不起来知识
    2018-08-13
    4
    95
  • Yoph
    方法句柄VS反射VS代理: 从访问控制层面来讲,反射需要调用setAccesible(),可能会受到安全管理器的禁止警告;代理有些情况下通过内部类实现,但是内部类只能访问受限的函数或字段;而方法句柄则在上下文中对所有方法都有完整的访问权限,并且不会受到安全管理器的限制,这是方法句柄的优势之一。 从执行速度层面来讲,在上一篇中老师也讲到了反射的性能会受到参数方法、类型的自动装箱和拆箱、方法内联的影响,相对来讲反射算是执行较慢的了(当然并没有和方法句柄通过执行具体操作示例作对比,可能在不同的JVM配置情况下执行情况不一样,比如解释器模式或编译模式下等);通过代理的方式因调用JAVA函数实现,速度与其它调用函数的速度是一样的,相对较快;而方法句柄可能不会有代理方式那样的执行速度快,但同样会受到JVM等不同的配置导致速度不同,但从JVM设计者的角度来说,应该是力求达到像调用函数一样快的速度,目前可能是达不到的。 从类的开销层面来讲,代理通常声明多个类,需要占用方法区,而方法句柄并不需要像代理一样有多个类的开销,不需要方法区的开销。
    2018-11-13
    1
    40
  • 飞翔明天
    老师说后面会简单一点,但是我到这篇,感觉好难,看不懂了
    2019-07-04
    2
    10
  • 李二木
    那么方法句柄是否可以取代反射了呢?
    2018-08-08
    9
  • 雨亦奇
    个人觉得还是按老师的课程安排来走吧。跳来跳去的讲可能会零散不系统。上面两位的说法我不赞同。
    2018-08-09
    7
  • lornsoul
    1.将句柄设为常量; 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-25
    5
  • Yoph
    方法句柄VS反射VS代理: 从访问控制层面来讲,反射需要调用setAccesible(),可能会受到安全管理器的禁止警告;代理有些情况下通过内部类实现,但是内部类只能访问受限的函数或字段;而方法句柄则在上下文中对所有方法都有完整的访问权限,并且不会受到安全管理器的限制,这是方法句柄的优势之一。 从执行速度层面来讲,在上一篇中老师也讲到了反射的性能会受到参数方法、类型的自动装箱和拆箱、方法内联的影响,相对来讲反射算是执行较慢的了(当然并没有和方法句柄通过执行具体操作示例作对比,可能在不同的JVM配置情况下执行情况不一样,比如解释器模式或编译模式下等);通过代理的方式因调用JAVA函数实现,速度与其它调用函数的速度是一样的,相对较快;而方法句柄可能不会有代理方式那样的执行速度快,但同样会受到JVM等不同的配置导致速度不同,但从JVM设计者的角度来说,应该是力求达到像调用函数一样快的速度,目前可能是达不到的。 从类的开销层面来讲,代理通常声明多个类,需要占用方法区,而方法句柄并不需要像代理一样有多个类的开销,不需要方法区的开销。
    2018-11-13
    5
  • Yoph
    方法句柄其实就是可以取得与反射相同的效果,不过方法句柄使用的代码更简洁。使用方法句柄,可以去掉反射中很多套路化的代码,提高代码的可读性。
    2018-11-13
    1
    4
收起评论
显示
设置
留言
38
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部