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

09 | JVM是怎么实现invokedynamic的?(下)

上回讲到,为了让所有的动物都能参加赛马,Java 7 引入了 invokedynamic 机制,允许调用任意类的“赛跑”方法。不过,我们并没有讲解 invokedynamic,而是深入地探讨了它所依赖的方法句柄。
今天,我便来正式地介绍 invokedynamic 指令,讲讲它是如何生成调用点,并且允许应用程序自己决定链接至哪一个方法中的。

invokedynamic 指令

invokedynamic 是 Java 7 引入的一条新指令,用以支持动态语言的方法调用。具体来说,它将调用点(CallSite)抽象成一个 Java 类,并且将原本由 Java 虚拟机控制的方法调用以及方法链接暴露给了应用程序。在运行过程中,每一条 invokedynamic 指令将捆绑一个调用点,并且会调用该调用点所链接的方法句柄。
在第一次执行 invokedynamic 指令时,Java 虚拟机会调用该指令所对应的启动方法(BootStrap Method),来生成前面提到的调用点,并且将之绑定至该 invokedynamic 指令中。在之后的运行过程中,Java 虚拟机则会直接调用绑定的调用点所链接的方法句柄。
在字节码中,启动方法是用方法句柄来指定的。这个方法句柄指向一个返回类型为调用点的静态方法。该方法必须接收三个固定的参数,分别为一个 Lookup 类实例,一个用来指代目标方法名字的字符串,以及该调用点能够链接的方法句柄的类型。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结
仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《深入拆解 Java 虚拟机》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(31)

  • 最新
  • 精选
  • Shine
    一直没理解“逃逸分析”啥意思?

    作者回复: 逃逸分析是指通过数据流分析,判断一个对象会不会被传递到当前编译的方法之外。比如说你调用了一个方法,将一个新建的对象作为参数传递出去,如果这个方法没有被内联,则说明该新建对象会逃逸。 逃逸分析是一项比较重要的优化,我后面会详细讲。

    27
  •  素丶  
    https://zhuanlan.zhihu.com/p/26389041 https://zhuanlan.zhihu.com/p/30936412 可以和 Shijie 大大的两篇文章配合着看。

    作者回复: 赞!

    3
    21
  • karl
    看了两遍 勉强有个概念了 还是基础不够 看不懂啊

    作者回复: invokedynamic涉及到的东西很多,底层实现也在不断改进。看懂个大概就好啦

    19
  • ext4
    我知道Java对Lambda有个规定:“The variable used in Lambda should be final or effectively final",也就是说Lambda表达式捕获的变量必须是final或等同于final的。而文中您又讲到:“对于捕获了变量的Lambda,每次invokedynamic都需要新建适配器类实例,以防止他们发生变化”。JVM之所以这么做,是因为这种final的要求仅限于Java source层面,在bytecode层面是是无法保证的。我理解的对吗?

    作者回复: 语言里的final,是对于当前方法调用而言的。这是因为它实际上就传了个值进去。比如说你定义了int a,然后传入 i -> i +a里,那么之后你对a的修改lambda是看不到的。 适配器针对的是多次不同调用,比如说每次调用你定义的final int a都不一样。

    16
  • Scott
    老师你好,我有两个问题,1是我看了几个有invokedynmaic指令的文件,都是invokedynamic #31, 0这种形式,似乎后面这个0没有什么作用,网上invokedynamic的解说也大多过时,我使用的是1.8.0_181版本。2. v10版本和v11版本性能的差距我猜想是v10版本不能正确的内联方法吧?虽然mh是final的,但是字节码层面已经丢失这个信息了。

    作者回复: 1. 这个数字0,指的是第几个bootstrap method,你多定义几个lambda,应该可以见到1 2 3等等。 2. 对的,是不能内联。不过,字节码中字段处还是会有final标志的。C2认为final实例字段在编译过程中不应该被认为是不变的,因为应用程序可能通过Unsafe来更改。Graal认为可以当成不变的,毕竟Java语言规范没有规定不可以。 V11的话,可以看出ConstantCallsite及时子类被特殊对待了。

    4
  • 小橙橙
    其实有个地方一直没有想透,为什么要学习字节码,学习字节码对我们日常开发有什么作用吗,老师能否给指点迷津一下?

    作者回复: 主要是了解底层实现。 对普通的日常开发可能作用不大。对于进阶的,比如分析应用的性能瓶颈,了解字节码将有所帮助。

    2
  • Void_seT
    单态内联缓存的实现代码段,bootstrap方法的实现有问题,没有return一个CallSite类型返回值。另外,这篇有点难度了,看了三遍,勉强理解。

    作者回复: 多谢指出!

  • lantern
    用invokedynamic实现lambda发生了什么是看懂了,但没有完全想明白这么做的必要性,这样做相比于编译时解lambda语法糖生成一个匿名类有什么好处呢 是因为对于不捕获局部变量的lambda不用反复new对象吗,那么对于需要捕获局部变量的情况invokedynamic的实现方式还有什么其他的好处吗
    1
    4
  • Kfreer
    如果该 Lambda 表达式捕获了其他变量,那么每次执行该 invokedynamic 指令,我们都要更新这些捕获了的变化。 问题:捕获的变量必须是final,为什么还会变呢,为什么会线程不安全呢?
    1
    4
  • 小鳄鱼
    老师,看来上一篇和这篇,又两个问题: 1. 尽管逃逸分析能够去除这些额外的新建实例开销,但是它也不是时时奏效。那么什么情况下不奏效呢? 2. 什么情况下编译器会将句柄识别成常量?除了本文中将MethodHandler定义为常量外,在其他什么情况下能识别为常量呢
    2
    3
收起评论
显示
设置
留言
31
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部