Module java.base

Package java.lang.invoke


package java.lang.invoke
java.lang.invoke包提供了与Java虚拟机交互的低级基元。

正如Java虚拟机规范中所述,该包中的某些类型受到虚拟机的特殊处理:

  • MethodHandleVarHandle包含签名多态方法,可以链接而不考虑它们的类型描述符。通常,方法链接需要精确匹配类型描述符。
  • JVM字节码格式支持类MethodHandleMethodType的立即常量。
  • invokedynamic指令利用引导MethodHandle常量动态解析CallSite对象,以实现自定义方法调用行为。
  • ldc指令利用引导MethodHandle常量动态解析自定义常量值。

调用站点和常量的动态解析

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

动态计算的调用站点

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

在JVM可以执行invokedynamic指令之前,必须首先对指令进行链接。链接是通过调用一个引导方法来完成的,该方法给出调用的静态信息内容,并且必须生成一个给出调用行为的CallSite

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

动态计算的常量

常量池中可能包含标记为CONSTANT_Dynamic的常量,配备执行其解析的引导方法。这样的动态常量最初处于未解析状态。在JVM可以使用动态计算的常量之前,必须首先对其进行解析。动态计算的常量解析是通过调用一个引导方法来完成的,该方法给出常量的静态信息内容,并且必须生成常量的静态声明类型的值。

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

引导方法的执行

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

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

  • 一个MethodHandles.Lookup,它是在动态计算的常量或调用站点所在的调用类上的查找对象
  • 一个String,在CONSTANT_NameAndType中提到的名称
  • 一个MethodTypeClassCONSTANT_NameAndType的解析类型描述符
  • 一个Class,如果是动态常量,则是常量的解析类型描述符
  • 额外的已解析静态参数(如果有)

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

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

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

解析的时机

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

如果存在多个这样的线程,则引导方法可能在多个线程中同时调用。因此,访问全局应用程序数据的引导方法必须采取防止竞态条件的常规预防措施。无论如何,每个invokedynamic指令要么未链接,要么链接到唯一的CallSite对象。

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

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

讨论:这些规则不允许JVM共享调用站点,也不允许发出“无因”的引导方法调用。每个invokedynamic指令最多从未链接转换为链接状态,就在其首次调用之前。没有办法撤消已完成引导方法调用的效果。

引导方法的类型

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

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

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

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

如果推送的值是原始类型,则可以通过装箱转换将其转换为引用。如果引导方法是可变参数方法(其修饰符位0x0080已设置),则这里指定的一些或所有参数可能会被收集到尾随数组参数中。 (这不是特殊规则,而是CONSTANT_MethodHandle常量、可变参数方法的修饰符位以及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 常量池条目直接提供,因为 asType 转换不执行必要的缩小原始类型转换。

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

自Java版本:
1.7