跳到主要内容

JEP 160:方法句柄的 Lambda 表达形式表示

QWen Max 中英对照

概述

通过使用可优化的中间表示替换汇编语言路径来改进方法句柄的实现,然后重构实现,使得更多的工作是在可移植的 Java 代码中完成,而不是硬编码到 JVM 中。

目标

  • 提高方法句柄和 invokedynamic 的性能、质量和可移植性。
  • 减少 JVM 中的汇编代码数量。
  • 降低方法句柄处理过程中本地调用及其他复杂控制转移的频率。
  • 增强现有 JVM 优化框架对 JSR 292 性能的影响。
  • 移除仅服务于 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 “lambdas”(将在 Java SE 8 中引入)的基础架构的一部分。总体而言,随着 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 表达式的类型系统被弱化为五个所谓的基本类型:引用(reference)、int、long、float 和 double。这意味着它们只能由受信任的 Java 代码创建。显式的类型转换和其他检查会在所有用户可访问的入口点保证类型安全。

作为新框架实现的相关优化,绑定方法句柄将被“扁平化”为包含其绑定值的小型结构体,几乎不需要装箱。这些结构体会根据需要进行组合和加载。

基于 ASM 库设计了用于 lambda 形式和携带少量数据的结构的字节码生成框架。虽然该框架旨在创建方法句柄,但也可以轻松扩展以创建其他类型的对象。因此,这项工作将为 Project Lambda 所需的功能性“SAM”对象,以及可能其他未来构造(如元组对象或混合数组)的高效表示提供可能的基础。

其他设计说明保存在 MLVM 仓库 中。

替代方案

我们可以保留汇编代码,并尝试改进现有的模式匹配器,使其适应于在没有调用点的情况下编译独立的方法句柄。

缺点是会增加 JVM 即时编译器(JITs)的复杂性,并且(可能)使优化工作的回报递减得更快。为所有目标平台维护手写的汇编代码会增加每个平台的成本,无论是新平台还是现有平台。特殊的栈帧类型(所谓的 ricochet frames)可能会增加必须遍历 JVM 栈的模块中出现 bug 的概率。

测试

现有的基于单元测试(jtreg)和“大型应用程序”的测试将继续进行。测试覆盖率将逐步提高。

客户提供的基准将用于检测性能的改进或退步。

依赖

这将是 JVM 和 JDK Java 代码库中的一个协调变更。(Java 代码的变更仅限于 java.lang.invokesun.invoke 包。)这些变更必须一起部署。

构建跨版本支持(旧的 JVM 和新的 JDK 和/或旧的 JDK 和新的 JVM)似乎并不实际。这意味着在平台能够运行 JDK 8 之前,必须先移植特定于平台的汇编代码。

我们期望将此代码反向移植到 JDK 7。这意味着我们需要在任何其他广泛的变化之前完成工作的主体部分(例如 JVM 重构),比如大规模的元数据变更 (JEP 147)元数据重定位 (JEP 122),这些变化将在 JDK 8 的代码库中发生。