找回密码
 会员注册
查看: 12|回复: 0

浅析JVMinvokedynamic指令和JavaLambda语法

[复制链接]

4

主题

0

回帖

13

积分

新手上路

积分
13
发表于 2024-10-8 13:45:29 | 显示全部楼层 |阅读模式
目录一、导语二、Java和JVM的关系三、JVM指令:invokedynamic四、方法句柄:MethodHandle五、Lambda表达式简介六、Lambda表达式实现? ? 1.?invokedynamic指令参数? ? 2.?期望的方法名称和描述符? ? 3.?BSM方法序号? ? 4. BSM方法? ? 5.?BSM方法参数? ? 6.?LambdaMetafactory#metafactory? ? 7. 构造CallSite? ? 8. 二阶段调用七、Lambda表达式性能八、Lambda表达式和final变量九、总结十、附录? ? 1. 自动生成的Lambda2适配类? ? 2. 自动生成的Lambda3适配类一导语尽管近年来JDK的版本发布愈发敏捷,当前最新版本号已经20+,但是日常使用中,JDK8还是占据了统治地位。你发任你发,我用Java8:【Jetbrains】2023 开发者生态系统现状 - https://www.jetbrains.com/zh-cn/lp/devecosystem-2023/java/JDK8如此旺盛的生命力,与其优异的兼容性、稳定性和足够日常开发使用的语言特性有极大的关系,这其中最引人瞩目的语言特性莫过于Lambda表达式。Lambda表达式语言特性引入Java语言后,赋予了Java语言更便捷的函数式编程魔力(相对匿名内部类),同时也让其更简洁,毕竟Java代码写起来啰嗦这点一直被开发者们广泛诟病。本文将从JVM和Java两个层面着手,和大家一起深入解析Lambda表达式。二Java和JVM的关系JVM是HLLVM(高级语言虚拟机),其参考物理计算机体系架构,设计、实现了一套特定领域虚拟指令集,即:字节码指令。利用上述虚拟指令集作为中间层,将上层高级语言和底层体系架构解耦以规避繁琐、复杂的平台兼容性问题,以实现【一次编译,处处运行】。Java是基于JVM提供的虚拟指令集,设计、实现的一种供开发者使用的高级语言。通过配套的编译器和标准库,将文本格式的Java代码编译成符合JVM指令集规范的二进制文件,交付到JVM执行。Java是一种运行在JVM平台上的高级语言,但是JVM平台绝不是只能运行Java语言。任何人都可以设计自己的语言语法,只要能按JVM规范编译成合法的JVM字节码,即可在JVM上运行(用Java命令)。计算机科学领域的任何问题,都可以通过增加一个中间层来解决。没有无源之水,Java语言层面的特性,除非是纯语法糖,不然一定离不开特定JVM特性的支撑。Lambda是Java8语言特性,那支撑它的便是JVM invokedynamic指令。三JVM指令:invokedynamic在Java7之前,JVM提供了如下4种【方法调用】指令:上述4种字节码指令各自有不同的使用场景,但是有一个共同的特点:目标方法一定需要在【编译期】确定。如下图,编译后4种指令的参数都指定了目标方法所在的类和签名以供运行时链接、动态分派。这个特点一方面保证了JVM语言类型安全,另一方面也限制了JVM平台对动态类型高级语言的支持。比如想让JavaScript、Python等动态语言代码编译成JVM字节码运行在JVM平台上的开销会比较大,性能也会比较差。为了解决上述问题, Java7引入了一条新的虚拟机指令:invokedynamic。这是自JVM 1.0以来第一次引入新的虚拟机指令,invokedynamic与其他 invoke*指令不同的是它允许由应用级的代码来决定方法解析(链接、分派)。所谓的【应用级的代码来决定方法解析】需要对照之前的invoke*指令来理解。之前的4种invoke*指令,在编译期就必须要明确目标方法并hardcode到字节码中,JVM在运行时直接解析、链接、动态分派硬编码指定的目标方法。而invokedynamic指令通过回调机制来获取需要调用的目标方法。即先调用业务自定义回调方法做方法决策(解析、链接),再调用其返回的目标方法。笔者称之为【两阶段调用】。伪代码对比如下:MethdoHandle为示意,后文有详述。伪字节码invokevirtual指令直接调用目标方法,invokedynamic直接调用回调方法,再调用回调方法返回的方法句柄。传统的invoke*指令直接调用字节码中指定的目标方法,如Son.testMethod1,invokedynamic指令在调用时,先调用字节码中指定的回调方法,如Son.dynamicMethodCallback,然后再调用回调方法(hook)返回的方法引用。而上述dynamicMethodCallback即为【应用级的代码或者我们常说的业务代码】,可以在不影响性能的前提下,灵活的干预JVM方法解析、链接的过程。总结来说,所谓应用级的代码其实也是一个方法,在这里这个方法被称为引导方法(Bootstrap Method),简称 BSM。invokedynamic执行时,BSM先被调用并返回一个 CallSite(调用点)对象,这个对象就和 invokedynamic链接在一起。以后再执行这条invokedynamic指令都不会创建新的 CallSite 对象。CallSite就是一个 MethodHandle(方法句柄)的holder,方法句柄指向一个调用点真正执行的方法。 一阶段:调用引导方法确定并缓存CallSite(MethodHandle)二阶段:调用CallSite(MethodHandle) 字节码指令比较low level,除字节码业务插桩场景外,字节码指令序列的构造、编排一般都由【高级语言编译器】来根据语言语法规则自动完成,如javac。 某种意义上有点类似Java【动态代理】机制,都是通过调用横切来动态桥接、灵活决策目标方法。四方法句柄:MethodHandle前面我们知道invokedynamic指令支持通过业务层面自定义的BSM来灵活的决策被调用的目标方法,也就是上述的【一阶段】。BSM方法的返回值就是【二阶段】调用的方法。但是和C、Python等语言不同,Java中方法/函数不是一等公民,也就是在Java中无法将【方法变量】作为方法返回值。为了解决这个问题,Java标准库提供了一个新的类型MethodHandle,用于实现类似C语言中的方法指针、JavaScript/Python中方法变量的能力。该API和反射API呈现的能力相似,但是性能更好。上述为MethodHandle API的基本使用,该课题展开又是一篇长文。总之,我们可以用MethodHandle来作为【方法变量】,变相的将【Java方法】提升为【一等公民】,从而可以在BSM中用Java代码实现动态编排、决策,返回合适的方法指针。这也是上述invokedynamic+BSM机制能够成立的一个基础。详见:秒懂Java之方法句柄(MethodHandle) (https://blog.csdn.net/ShuSheng0007/article/details/107066856) 上述【一阶段】调用的本质就是得到一个特定的MethodHandle(方法指针/方法引用),【二阶段】调用就是调用这个MethodHandle。五Lambda表达式简介Java的Lambda表达式,是传统的【匿名内部类】特性在特定场景下的平替特性。所谓的特定场景,即我们熟知的FunctionalInterface。当【匿名内部类】匿名实现的是一个FunctionalInterface时,可以用Lambda表达式平替。示例如下:函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。Java 不会强制要求你使用 @FunctionalInterface 注解来标记你的接口是函数式接口,然而,作为API作者,你可能倾向使用@FunctionalInterface指明特定的接口为函数式接口,这只是一个设计上的考虑,可以让用户很明显的知道一个接口是函数式接口。Java Lambda表达式在语法层面有两种形式:行内代码块、方法引用。但是在编译产物中,行内Lambda最终会被提取到独立的静态方法中。也就是说,在字节码层面只有【方法引用】一种Lambda形式。如上图反编译结果,两个行内Lambda中的代码在编译后被提取到两个自动生成的方法lambda$main$0、lambda$main$1,后续Lambda表达式的处理流程都可以收敛,无需区分对待。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2025-1-10 06:13 , Processed in 0.477095 second(s), 25 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表