JEP 160: Lambda-Form Representation for Method Handles | 方法句柄的 Lambda 表达式表示
摘要
通过用可优化的中间表示替换汇编语言路径,然后重构实现,以便在可移植的 Java 代码中完成更多工作,而不是硬编码到 JVM 中,从而改进方法句柄的实现。
目标
- 提高方法句柄和
invokedynamic
的性能、质量和可移植性。 - 减少 JVM 中的汇编代码量。
- 减少在方法句柄处理过程中本地调用和其他复杂控制转换的频率。
- 提高现有 JVM 优化框架对 JSR 292 性能的影响。
- 从 JVM 中移除仅服务于 JSR 292 的低效或复杂结构。(例如,移除模式匹配的“方法句柄遍历”阶段。)
- 完全符合 Java SE 7 规范中关于 JSR 292 的要求。
- 提供 JSR 292 的更好参考实现。
非目标
这项工作旨在为未来的优化工作奠定基础,这些优化工作将在 JVM 和 Java 代码中分别进行。因此,只要系统明显更加简单、更好地分解并且更容易优化,本项目就会在性能和稳定性方面取得适度的改进。在这种情况下,即使简化了 Java 7 的代码库,性能也不会受到损害,这就足够了。
动机
JDK 7 中对 JSR 292(方法句柄和 invokedynamic
)的实现依赖于大量的手写汇编代码来执行方法句柄参数转换。优化的本地代码由一个单独的模块获得,该模块在方法句柄图上执行模式匹配遍历,并将它们(在 JVM JIT 内部)转换为中间表示(Java 字节码,然后是 C1 或 C2 IR)。
这种架构对于许多用途来说是足够的,但存在两个缺陷。首先,非常量方法句柄的调用无法进行优化,因为模式匹配转换为 IR 仅在调用点(如 invokedynamic
)发生。此类调用必须将参数列表从编译格式复制到解释格式,然后执行手写汇编代码进行参数转换,导致数据移动过多且未优化。客户将此体验为“性能悬崖”。
其次,由于方法句柄的解释版本和编译版本使用不同的执行引擎(汇编代码与从模式匹配生成的 IR),并且由于表示之间的转换不完善,编译后的方法句柄的行为并不总是与解释后的方法句柄相同。特别是,当转换后的方法句柄变得过大且其字节码包含太多符号引用时,会间歇性地出现 NoClassDefFoundError
。
可以通过移除汇编代码并用同时用于解释和编译的中间表示来替换它,从而消除这些缺陷。
作为次要效果,移除汇编代码将使 JVM 更容易移植到其他平台。
随着 Java SE 8 中即将引入的 Java“lambda”表达式,非常量方法句柄的调用将变得更加频繁,因为方法句柄是 Java lambda 表达式的基础设施的一部分。一般来说,随着 JSR 292 的广泛应用,它必须在更广泛的使用案例中保持更稳健的性能。
描述
为方法句柄创建一种新的中间表示形式,称为 lambda 形式,该形式(a)可直接执行,并且(b)可直接且简单地简化为字节码和 / 或 JIT IR。使用 lambda 形式实现所有方法句柄操作和 invokedynamic
调用点。
从 methodHandles_<arch>.cpp
中移除所有汇编代码,但保留一小部分汇编指令(约 100 条),用于 lambda 形式使用的子原语。(相比之下,JDK 7 为众多用户可见的参数转换生成方法句柄存根,其中包含约 7000 条汇编指令。)
在可能的情况下,将特定于方法句柄的实现逻辑从 JVM 上移到 Java 代码中。依赖 JIT 对 lambda 形式(或其对应的字节码)及其子原语进行强有力的优化。
lambda 形式表示一系列弱类型化的正式参数,后跟一系列线性、无分支的方法调用表达式。每个表达式由一个方法(指定为任意常量方法句柄)及其相关参数组成。参数可以是任意常量或对 lambda 形式内先前参数或表达式的引用。
子原语包括用于原始方法句柄调用的低级适配器和用于模拟四种调用模式(invokevirtual
等)的适配器。
lambda 形式可以随时编译成紧凑的 Java 字节码,并传递给 JVM 进行动态加载。lambda 形式将包含它们自己的调用计数器,这将允许它们延迟编译,直到它们“足够热”。JVM 的未来版本可能能够直接执行和 / 或编译和 / 或分析 lambda 形式,从而导致混合模式执行和优化主题的进一步变化。
为了最大限度地重用 lambda 形式及其编译后的代码,lambda 形式表达式的类型系统被简化为五个所谓的 基本类型:引用、int
、long
、float
和 double
。这意味着它们只能由受信任的 Java 代码创建。显式转换和其他检查在所有用户可访问的入口点处保持类型安全。
作为新框架带来的相关优化,绑定方法句柄将被“扁平化”为包含其绑定值的小型结构体,几乎没有或没有装箱。这些结构体将根据需要组合和加载。
lambda 形式和小型数据承载结构体的字节码生成框架基于 ASM 库,尽管设计用于创建方法句柄,但可以轻松扩展到创建其他类型的对象。因此,这项工作将为 Project Lambda 所需的功能性“SAM”对象的有效表示提供可能的基础,以及未来可能的其他构造,如元组对象或混合数组。
其他设计说明保存在 MLVM 存储库中。
替代方案
我们可以保留汇编代码,并尝试改进现有的模式匹配器,并使其能够编译独立的方法句柄(在没有调用点的情况下)。
缺点是 JVM JIT 的复杂性增加,并且(可能)优化工作的回报迅速减少。为所有目标平台维护手写汇编器将增加每个平台的新旧成本。特殊的栈帧类型(所谓的 弹跳帧)可能会增加必须遍历 JVM 栈的模块中出现错误的可能性。
测试
将继续进行现有的单元测试(基于 jtreg)和“大型应用程序”测试。测试覆盖率将逐渐提高。
将使用客户衍生的基准测试来检测性能改进或回归。
依赖关系
这将是 JVM 和 JDK Java 代码库中的协调更改。(Java 代码更改仅限于 java.lang.invoke
和 sun.invoke
包。)更改必须一起部署。
在跨版本支持(旧 JVM 和新 JDK 和 / 或旧 JDK 和新 JVM)中构建似乎不切实际。这意味着在平台能够运行 JDK 8 之前,必须移植特定于平台的汇编代码。
我们计划将此代码回迁到 JDK 7。这意味着我们需要在 JDK 8 代码库中进行其他普遍更改(如大规模 元数据更改(JEP 147) 或 元数据重定位(JEP 122))之前,完成工作的主要部分(如 JVM 重构)。