JEP 309:动态类文件常量
概括
扩展 Java 类文件格式以支持新的常量池形式CONSTANT_Dynamic
.加载意志CONSTANT_Dynamic
将创建委托给引导方法,就像链接调用invokedynamic
站点将链接委托给引导方法一样。
目标
我们寻求降低创建新形式的可具体化类文件常量的成本和中断,这反过来又为语言设计者和编译器实现者提供了更广泛的表达性和性能选择。我们通过创建一个新的常量池形式来实现这一点,该常量池形式可以使用用户提供的行为进行参数化,以带有静态参数的引导方法的形式。
我们还将调整 JVM 和引导方法之间的链接时握手,以便调整所使用的引导 APIinvokedynamic
也适用于动态常量。
根据经验,invokedynamic
我们将对两者和动态常量的引导握手进行调整invokedynamic
,放宽对引导方法处理参数列表的某些限制。
这项工作需要对 JDK 库支持进行一些原型设计,以支持几种常量类型的代表性示例,尤其是变量句柄(JEP 193)。为了支持此类原型设计,这项工作将与有关常量表达式的基本语言支持的其他工作协调(JEP 303)。
非目标
该 JEP 旨在支持常量池中的任意常量。尽管还有一些关于引导方法的其他用途的建议,例如方法配方,但此 JEP 集中于一种用途。
该 JEP 的成功并不依赖于 Java 语言或 Java 编译器后端的支持,尽管如果由编译器后端使用它更可能成功。
尽管大型聚合常量是 Java 转换策略中的一个弱点,但该 JEP 无法解决聚合问题,除非有更好的方法将它们封装为常量形式,例如冻结数组或基元专 用列表。
成功指标
int.class
作为最低要求,公开常量池形式来描述原始类镜像 ( )、null
、enum
常量以及 的大多数形式VarHandle
应该是实用的CONSTANT_Dynamic
。
动态常量必须可在当前允许通用常量池常量的任何上下文中使用,例如CONSTANT_String
和CONSTANT_MethodType
。因此,它们必须是ldc
指令的有效操作数,并且必须允许作为引导方法的静态参数。
引导方法握手应该支持包含数千个组件参数的复杂常量,从而提高了 251 个常量参数的当前限制。作为延伸目标,引导方法还应该有一种方法来更准确地控制通过解析引导方法参数而产生的链接错误。
在工作结束时,我们还应该有理由相信这种机制可以适用于各种库类型,例如派生方法句柄、小型不可变集合(列表、映射、集合)、数字、正则表达式、字符串格式化程序或简单的数据类。
应确定并记录后续工作。请参阅下面的“可能的扩展”。
动机
Java虚拟机规范的4.4节描述了常量池的格式。添加新的常量池形式(例如 Java 7 中的支持MethodHandle
和MethodType
引入)是一项重大工作,并且会在整个生态系统中产生连锁反应,因为它会影响解析或解释类文件的所有代码。这对创建新的常量池形式提出了非常高的门槛。
使用invokedynamic
,在常量池中存储复杂数据的价值会成倍增加,因为invokedynamic
引导程序的静态参数列表是常量序列。协议的设计者invokedynamic
(例如LambdaMetafactory
Java 8 中添加的协议)通常会遇到根据现有常量集对行为进行编码的需要,这反过来又需要在引导程序本身中添加容易出错的额外验证和提取逻辑。更丰富、更灵活、更高类型的常量消除了协议开发中的摩擦invokedynamic
,从而促进了复杂逻辑从运行时到链接时的移动,提高了程序性能并简化了编译器逻辑。
描述
正如调用站点的链接invokedynamic
涉及从 JVM 到基于 Java 的链接逻辑的上行调用一样,我们可以将相同的技巧应用于常量池条目的解析。常量CONSTANT_Dynamic
池条目对引导方法进行编码以执行解析 (a MethodHandle
)、常量的类型 (a Class
) 和任何静态引导参数(常量的任意序列,禁止常量池中动态常量之间的循环。)
我们添加一个新的常量池形式CONSTANT_Dynamic
(新常量标签 17),它的标签字节后面有两个组件:引导方法的索引,其格式与 a 中的索引相同CONSTANT_InvokeDynamic
,以及 a CONSTANT_NameAndType
,它编码预期的类型。
在行为上,CONSTANT_Dynamic
通过对以下参数执行其引导方法来解析常量:1. 本地Lookup
对象,2.String
表示常量的名称组件,3.Class
表示预期常量类型,以及 4. 任何剩余的引导参数。与 一样invokedynamic
,多个线程可以竞相解决,但将选择一个唯一的获胜者,并丢弃任何其他有争议的答案。引导方法不会按照指令要求返回CallSite
对象,而是invokedynamic
返回一个值,该值将立即转换为所需的类型。
与 一样invokedynamic
,名称组件是除类型之外的附加通道,用于将表达式信息传递给引导方法。预计正如invokedynamic
指令查找名称组件(例如,方法名称或某些临时描述符)的用途一样,动态常量也会查找名称的用途(例如,常量的名称enum
或符号常量的拼写) )。将 放在这CONSTANT_NameAndType
两个地方可以使设计更加常规。实际上,CONSTANT_Methodref
和CONSTANT_Fieldref
常量用于引用类的命名成员,而类似的CONSTANT_InvokeDynamic
和CONSTANT_Dynamic
常量用于引用具有用户编程引导程序的命名实体。
常量的类型组件,与invokedynamic
和一起CONSTANT_Dynamic
,分别确定调用站点或常量的有效类型。引导方法不会贡献或限制此类型信息,因此引导方法可能(并且通常是)弱类型,而字节码本身始终是强类型。
为了放宽引导说明符的长度限制,定义引导方法调用的语言将被调整(具有完全向后兼容性),以允许变量 arity ( ACC_VARARGS
) 引导方法将所有剩余的静态参数吸收到其尾随参数中,即使存在其中有 2^16-1 个。 (类文件格式已经允许这样做,尽管无法读取过长的引导参数列表。)为了保持一致性,如果目标方法具有可变数量,则invokeWithArguments
的方法也将以这种方式扩展。MethodHandle
这样,引导方法的调用就可以用弱类型方法 和 来指定invokeWithArguments
,就像今天用aloneinvoke
来指定一样。invoke
引导程序链接错误的控制已被证明是用户的错误和 RFE 的重复来源invokedynamic
,并且随着引导程序方法变得更加复杂(因为它们 必须具有动态常量),这种趋势可能会加速。如果我们能够找到一种方法来对引导方法的异常提供更全面的控制,并且可以简单地完成,我们将考虑将其作为此 JEP 的一部分提供。否则,它将出现在未来增强功能的列表中。
Java 虚拟机规范草案可以在JDK-8189199CONSTANT_Dynamic
中找到,CSR 问题与该 JEP 的主要开发问题相关。
未来的工作
未来可能的扩展包括:
- 支持批量常量,例如数组或资源表
- 对引导方法握手的进一步调整
- 可以与动态常量协同作用的引导方法的其他用途
- 将动态常量附加到
ConstantValue
静态字段的属性 - 在 Java 语言中展示常量的延迟初始化
- 将新常量与常量表达式的特殊 Java 语言规则集成
设计选择的讨论可以在JDK-8161256中找到,它涉及许多相关的 RFE。目前的 JEP 是从这个更大的功能列表中提炼出来的。
备择方案
的许多用法CONSTANT_Dynamic
可以用等效的调用来替换invokedynamic
。 (该调用将采用零个参数,并绑定到返回所需常量的方法句柄。)但是,这种解决方法无助于满足关键要求,即能够将合成常量作为引导参数传递。
另一种替代方法CONSTANT_Dynamic
是使 用static final
字段来命名所需的常量,并在静态初始值设定项 ( <clinit>
) 中计算它们的值。这种方法需要额外的元数据(每个常量的一次性字段定义),并且不够懒惰,无法避免引导循环问题。这些问题通常通过使用解耦的静态初始化器构建私有嵌套类来解决,但这也需要额外的元数据。如果语言发展到使用许多这样的常量,过多的元数据将会导致应用程序膨胀。
另一种方法是旋转静态方法,该方法执行不断的细化逻辑,然后从invokedynamic
.同样,这种一次性方法是元数据开销,与CONSTANT_Dynamic
.
实际上,模拟这些功能的元数据开销太大。
依赖关系
此功能以 JVM 为中心,因此不依赖于更高的软件层。
为了确保正确的设计,至少需要通过多个用例进行实验采用。库原型设计是必须的,即使原型被扔掉了。
与 一样invokedynamic
,广泛采用需要后端使用javac
,而后端可能需要语言扩展。作为基本的第一步,int.class
如果可能的话,应该检查需要隐藏静态方法的转换解决方法,例如转换或切换映射表,并用新常量重新表述。