JEP 160:方法句柄的 Lambda 形式表示
概括
通过用可优化的中间表示替换汇编语言路径,然后重构实现,改进方法句柄的实现,以便在可移植 Java 代码中完成比硬连线到 JVM 中更多的工作。
目标
- 提高方法句柄和 的性能、质量和可移植性
invokedynamic
。 - 减少 JVM 中的汇编代码量。
- 减少方法句柄处理期间本机调用和其他复杂的控制转换的频率。
- 提高现有 JVM 优化框架对 JSR 292 性能的利用。
- 从 JVM 中删除仅服务于 JSR 292 的低杠杆或复杂结构。 (例如,删除模式匹配的“方法句柄遍历”阶段。)
- 与 JSR 292 的 Java SE 7 规范完全兼容。
- JSR 292 的更好参考实现。
非目标
这项工作旨在为未来的优化工作奠定基础,分别在 JVM 和 Java 代码中进行。因此,这个项目将会成功,并在性能和稳定性方面有适度的改进。只要系统明显更简单、分解得更好并且更容易优化,就不需要大幅提高性能。如果简化 Java 7 代码库后性能没有受到损害,这一点就会很清楚。
动机
JSR 292(方法句柄 和 )的 JDK 7 实现invokedynamic
依赖于大量手写的汇编代码来执行方法句柄参数转换。优化的本机代码是通过一个单独的模块获得的,该模块对方法句柄图执行模式匹配遍历,并将它们(在 JVM JIT 内部)转换为中间表示(Java 字节码,然后是 C1 或 C2 IR)。
这种架构足以满足多种用途,但有两个缺陷。首先,非常量方法句柄的调用无法优化,因为到 IR 的模式匹配转换仅发生在调用站点(例如invokedynamic
)。此类调用必须将参数列表从编译格式复制为解释格式,然后执行手写的汇编代码进行参数转换,从而导致数据移动过多且未优化。客户将其体验为“性能悬崖”。
其次,由于方法句柄的解释版本和编译版本使用不同的执行引擎(汇编代码与从模式匹配生成的 IR),并且由于表示之间的转换不完美,因此编译方法句柄的行为并不总是与解释方法句柄相同。特别是NoClassDefFoundError
当翻译的方法句柄变得太大并且它们的字节码包含太多符号引用时,会导致间歇性的情况。
可以通过删除汇编代码并将其替换为用于解释和编译的中间表示来消除这些缺陷。
作为次 要效果,删除汇编代码将使将 JVM 移植到其他平台变得更容易。
非常量方法句柄调用在未来将变得更加频繁,因为方法句柄是 Java SE 8 中即将出现的 Java“lambda”基础结构的一部分。一般来说,随着 JSR 292 被更广泛地采用,它必须变得更加频繁。性能强大,适用于更广泛的用例。
描述
为方法句柄创建一个新的中间表示,称为_lambda 形式_,它 (a) 可以直接执行,(b) 可以直接简单地简化为字节码和/或 JIT IR。使用 lambda 形式实现所有方法句柄操作和invokedynamic
调用站点。
从 中删除所有汇编代码methodHandles_<arch>.cpp
,除了 lambda 形式使用的子原语的少量汇编指令(大约 100 条)之外。 (相比之下,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 库。虽然设计用于创建方法句柄,但它可以轻松扩展以创建其他类型的对象。因此,这项工作将为 Lambda 项目所需的功能“SAM”对象以及其他未来构造(例如元组对象或混合数组)的有效表示提供可能的基础。
其他设计说明保存在 MLVM 存储库中。
备择方案
我们可以保留汇编代码并尝试改进现有的模式匹配器并使其适应编译独立的方法句柄(在没有调用站点的情况下)。
缺点是 JVM JIT 的复杂性增加,并且(可能)优化工作的回报会更快地递减。为所有目标平台维护手写汇编程序将增加每个平台的成本,无论是新平台还是现有平台。特殊的堆栈帧类型(所谓的_跳跳帧_)可能会增加必须遍历 JVM 堆栈的模块中出现错误的可能性。
测试
现有的单元测试(基于 jtreg)和“大型应用程序”测试将继续。测试覆盖率将逐步提高。
客户衍生的基准将用于检测性能改进或回归。
依赖关系
这将是 JVM 和 JDK Java 代码库的协调变化。 (Java 代码更改仅限于 java.lang.invoke 和 sun.invoke 包。)这些更改必须一起部署。
构建跨版本支持(旧 JVM 和新 JDK 和/或旧 JDK 和新 JVM)似乎不切实际。这意味着必须先移植特定于平台的汇编代码,然后平台才能运行 JDK 8。
我们希望将此代码向后移植到 JDK 7。这意味着我们需要在任何其他普遍更改(例如大规模元数据更改 (JEP 147)或元数据重定位)之前完成工作的主体(例如 JVM 重构)(JEP 122),发生在 JDK 8 代码库中。