模块 java.base

包 java.lang.invoke


java.lang.invoke
java.lang.invoke 包提供用于与 Java 虚拟机交互的低级原语。

如 Java 虚拟机规范中所述,此包中的某些类型由虚拟机给予特殊处理:

  • MethodHandle VarHandle 包含 签名多态方法 ,无论它们的类型描述符如何都可以链接。通常,方法链接需要类型描述符的精确匹配。
  • JVM 字节码格式支持类 MethodHandle MethodType 的立即常量。
  • invokedynamic 指令使用引导程序 MethodHandle 常量来动态解析 CallSite 对象以实现自定义方法调用行为。
  • ldc 指令使用引导程序 MethodHandle 常量来动态解析自定义常量值。

调用点和常量的动态解析

以下低级信息总结了 Java 虚拟机规范的相关部分。有关详细信息,请参阅该规范的当前版本。

动态计算的调用点

invokedynamic 指令最初处于未链接状态。在此状态下,没有要调用的指令的目标方法。

在 JVM 可以执行 invokedynamic 指令之前,该指令必须首先是 linked 。链接是通过调用一个 bootstrap method 来完成的,它被赋予了调用的静态信息内容,并且它必须产生一个 CallSite 来给出调用的行为。

每个 invokedynamic 指令静态地将其自己的引导方法指定为常量池引用。常量池引用还指定了调用的名称和方法类型描述符,就像invokestatic 和其他调用指令一样。

动态计算常量

常量池可能包含标记为 CONSTANT_Dynamic 的常量,配备了执行其解析的引导方法。这样的dynamic constant本来就处于悬而未决的状态。在 JVM 可以使用动态计算的常量之前,它必须首先是 resolved 。动态计算的常量解析是通过调用 bootstrap method 来完成的,它被赋予了常量的静态信息内容,并且必须产生常量的静态声明类型的值。

每个动态计算的常量静态指定其自己的引导方法作为常量池引用。常量池引用还指定了常量的名称和字段类型描述符,就像getstatic 和其他字段引用说明一样。 (粗略地说,动态计算的常量对于动态计算的调用点就像 CONSTANT_Fieldref 对于 CONSTANT_Methodref 一样。)

引导方法的执行

解析动态计算的调用点或常量首先从常量池中为以下项目解析常量:
  • 引导方法,一个CONSTANT_MethodHandle
  • ClassMethodType 派生自 CONSTANT_NameAndType 描述符的类型组件
  • 静态参数,如果有的话(注意静态参数本身可以是动态计算的常量)

然后调用 bootstrap 方法,就像通过 MethodHandle.invoke 一样,使用以下参数:

  • 一个 MethodHandles.Lookup ,它是 caller class 上的查找对象,其中出现动态计算常量或调用站点
  • 一个StringCONSTANT_NameAndType中提到的名字
  • MethodTypeClassCONSTANT_NameAndType 的解析类型描述符
  • Class ,常量的解析类型描述符,如果它是动态常量
  • 额外的解析静态参数,如果有的话

对于动态计算的调用站点,返回的结果必须是对 CallSite 的非空引用。调用站点目标的类型必须完全等于从调用的类型描述符派生并传递给引导方法的类型。如果不满足这些条件,则会抛出 BootstrapMethodError。成功后,调用站点将永久链接到 invokedynamic 指令。

对于动态计算的常量,引导程序方法的第一个参数必须可分配给 MethodHandles.Lookup 。如果不满足此条件,则会抛出 BootstrapMethodError。成功时,引导程序方法的结果被缓存为已解析的常量值。

如果 bootstrap 方法执行过程中发生异常,比如E,则解析失败并异常终止。如果 E 的类型是 Error 或子类,则重新抛出 E,否则抛出包装 EBootstrapMethodError。如果发生这种情况,所有后续尝试执行 invokedynamic 指令或加载动态计算常量的尝试都会抛出相同的错误。

解决时间

invokedynamic 指令在其第一次执行之前被链接。动态计算的常量在第一次使用之前被解析(通过将其压入堆栈或将其链接为引导方法参数)。实现链接的引导方法调用发生在尝试首次执行或首次使用的线程中。

如果有多个这样的线程,则可以在多个线程中同时调用引导方法。因此,访问全局应用程序数据的引导方法必须采取通常的预防措施来防止竞争条件。在任何情况下,每个 invokedynamic 指令都未链接或链接到唯一的 CallSite 对象。

在需要 invokedynamic 具有单独可变行为的指令的应用程序中,它们的引导方法应该产生不同的 CallSite 对象,每个链接请求一个。或者,应用程序可以将单个 CallSite 对象链接到多个 invokedynamic 指令,在这种情况下,目标方法的更改将在每个指令中可见。

如果多个线程同时为单个动态计算的调用点或常量执行引导方法,则 JVM 必须选择一个引导方法结果并将其安装到所有线程可见。允许完成任何其他引导方法调用,但忽略它们的结果。

Discussion: 这些规则不允许 JVM 共享调用站点,或发出“无原因的”引导方法调用。每个 invokedynamic 指令最多从未链接到链接的转换一次,就在它的第一次调用之前。无法撤销已完成的引导方法调用的效果。

自举方法的类型

对于动态计算的调用点,bootstrap 方法使用参数类型 MethodHandles.LookupStringMethodType 和任何静态参数的类型调用;返回类型是 CallSite

对于动态计算的常量,bootstrap 方法使用参数类型 MethodHandles.LookupStringClass 和任何静态参数的类型调用;返回类型是由 Class 表示的类型。

因为 MethodHandle.invoke 允许在调用的方法类型和引导方法句柄的方法类型之间进行适配,所以引导方法的声明具有灵活性。对于动态计算常量,引导方法句柄的第一个参数类型必须可分配给 MethodHandles.Lookup ,除此约束外,相同程度的灵活性适用于动态计算调用站点和动态计算常量的引导方法。注意:此约束允许将来仅使用静态参数的参数类型调用引导方法的可能性,从而支持更广泛的与静态参数兼容的方法(例如不声明或不需要查找的方法,名称和类型元数据参数)。

例如,对于动态计算的调用点,第一个参数可以是 Object 而不是 MethodHandles.Lookup ,返回类型也可以是 Object 而不是 CallSite 。 (请注意,堆栈参数的类型和数量将引导方法的合法种类限制为适当类型的静态方法和构造函数。)

如果推送的值是原始类型,则可以通过装箱转换将其转换为引用。如果 bootstrap 方法是可变元数方法(其修饰符位 0x0080 已设置),则此处指定的部分或全部参数可能会收集到尾随数组参数中。 (这不是特殊规则,而是 CONSTANT_MethodHandle 常量、变量 arity 方法的修饰符位和 asVarargsCollector 转换之间相互作用的有用结果。)

鉴于这些规则,这里是动态计算的调用站点的合法引导方法声明的示例,给定各种数量N的额外参数。第一行(标记为 * )适用于任意数量的额外参数。

静态参数类型
N 示例自举方法
*
  • CallSite bootstrap(Lookup caller, String name, MethodType type, Object... args)
  • CallSite bootstrap(Object... args)
  • CallSite bootstrap(Object caller, Object... nameAndTypeWithArgs)
0
  • CallSite bootstrap(Lookup caller, String name, MethodType type)
  • CallSite bootstrap(Lookup caller, Object... nameAndType)
1 CallSite bootstrap(Lookup caller, String name, MethodType type, Object arg)
2
  • CallSite bootstrap(Lookup caller, String name, MethodType type, Object... args)
  • CallSite bootstrap(Lookup caller, String name, MethodType type, String... args)
  • CallSite bootstrap(Lookup caller, String name, MethodType type, String x, int y)
最后一个示例假定额外参数的类型分别为 StringInteger(或 int)。倒数第二个示例假定所有额外参数都是 String 类型。其他示例适用于所有类型的额外参数。请注意,如果返回类型更改为与常量的声明类型兼容(例如 Object ,它始终兼容),则除第二个和第三个示例之外的所有示例也适用于动态计算的常量。

由于动态计算的常量可以作为静态参数提供给引导程序方法,因此对引导程序参数的类型没有限制。但是,booleanbyteshortchar 类型的参数不能由 CONSTANT_Integer 常量池条目 directly 提供,因为 asType 转换不执行必要的缩小原始转换。

在上面的示例中,返回类型始终是 CallSite ,但这不是引导方法的必要特征。在动态计算调用站点的情况下,唯一的要求是引导方法的返回类型必须可转换(使用 asType 转换)到 CallSite ,这意味着引导方法返回类型可能是 ObjectConstantCallSite 。在动态解析常量的情况下,引导程序方法的返回类型必须可转换为常量的类型,由其字段类型描述符表示。例如,如果动态常量的字段类型描述符为 "C" (char),则引导方法返回类型可以是 ObjectCharacterchar,但不能是 intInteger

自从:
1.7