包 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
Class
或MethodType
派生自CONSTANT_NameAndType
描述符的类型组件- 静态参数,如果有的话(注意静态参数本身可以是动态计算的常量)
然后调用 bootstrap 方法,就像通过 MethodHandle.invoke
一样,使用以下参数:
- 一个
MethodHandles.Lookup
,它是 caller class 上的查找对象,其中出现动态计算常量或调用站点 - 一个
String
,CONSTANT_NameAndType
中提到的名字 MethodType
或Class
,CONSTANT_NameAndType
的解析类型描述符Class
,常量的解析类型描述符,如果它是动态常量- 额外的解析静态参数,如果有的话
对于动态计算的调用站点,返回的结果必须是对 CallSite
的非空引用。调用站点目标的类型必须完全等于从调用的类型描述符派生并传递给引导方法的类型。如果不满足这些条件,则会抛出 BootstrapMethodError
。成功后,调用站点将永久链接到 invokedynamic
指令。
对于动态计算的常量,引导程序方法的第一个参数必须可分配给 MethodHandles.Lookup
。如果不满足此条件,则会抛出 BootstrapMethodError
。成功时,引导程序方法的结果被缓存为已解析的常量值。
如果 bootstrap 方法执行过程中发生异常,比如E
,则解析失败并异常终止。如果 E
的类型是 Error
或子类,则重新抛出 E
,否则抛出包装 E
的 BootstrapMethodError
。如果发生这种情况,所有后续尝试执行 invokedynamic
指令或加载动态计算常量的尝试都会抛出相同的错误。
解决时间
invokedynamic
指令在其第一次执行之前被链接。动态计算的常量在第一次使用之前被解析(通过将其压入堆栈或将其链接为引导方法参数)。实现链接的引导方法调用发生在尝试首次执行或首次使用的线程中。
如果有多个这样的线程,则可以在多个线程中同时调用引导方法。因此,访问全局应用程序数据的引导方法必须采取通常的预防措施来防止竞争条件。在任何情况下,每个 invokedynamic
指令都未链接或链接到唯一的 CallSite
对象。
在需要 invokedynamic
具有单独可变行为的指令的应用程序中,它们的引导方法应该产生不同的 CallSite
对象,每个链接请求一个。或者,应用程序可以将单个 CallSite
对象链接到多个 invokedynamic
指令,在这种情况下,目标方法的更改将在每个指令中可见。
如果多个线程同时为单个动态计算的调用点或常量执行引导方法,则 JVM 必须选择一个引导方法结果并将其安装到所有线程可见。允许完成任何其他引导方法调用,但忽略它们的结果。
Discussion: 这些规则不允许 JVM 共享调用站点,或发出“无原因的”引导方法调用。每个 invokedynamic
指令最多从未链接到链接的转换一次,就在它的第一次调用之前。无法撤销已完成的引导方法调用的效果。
自举方法的类型
对于动态计算的调用点,bootstrap 方法使用参数类型MethodHandles.Lookup
、 String
、 MethodType
和任何静态参数的类型调用;返回类型是 CallSite
。
对于动态计算的常量,bootstrap 方法使用参数类型 MethodHandles.Lookup
、 String
、 Class
和任何静态参数的类型调用;返回类型是由 Class
表示的类型。
因为 MethodHandle.invoke
允许在调用的方法类型和引导方法句柄的方法类型之间进行适配,所以引导方法的声明具有灵活性。对于动态计算常量,引导方法句柄的第一个参数类型必须可分配给 MethodHandles.Lookup
,除此约束外,相同程度的灵活性适用于动态计算调用站点和动态计算常量的引导方法。注意:此约束允许将来仅使用静态参数的参数类型调用引导方法的可能性,从而支持更广泛的与静态参数兼容的方法(例如不声明或不需要查找的方法,名称和类型元数据参数)。
例如,对于动态计算的调用点,第一个参数可以是 Object
而不是 MethodHandles.Lookup
,返回类型也可以是 Object
而不是 CallSite
。 (请注意,堆栈参数的类型和数量将引导方法的合法种类限制为适当类型的静态方法和构造函数。)
如果推送的值是原始类型,则可以通过装箱转换将其转换为引用。如果 bootstrap 方法是可变元数方法(其修饰符位 0x0080
已设置),则此处指定的部分或全部参数可能会收集到尾随数组参数中。 (这不是特殊规则,而是 CONSTANT_MethodHandle
常量、变量 arity 方法的修饰符位和 asVarargsCollector
转换之间相互作用的有用结果。)
鉴于这些规则,这里是动态计算的调用站点的合法引导方法声明的示例,给定各种数量N
的额外参数。第一行(标记为 *
)适用于任意数量的额外参数。
N | 示例自举方法 |
---|---|
* |
|
0 |
|
1 | CallSite bootstrap(Lookup caller, String name, MethodType type, Object arg) |
2 |
|
String
和 Integer
(或 int
)。倒数第二个示例假定所有额外参数都是 String
类型。其他示例适用于所有类型的额外参数。请注意,如果返回类型更改为与常量的声明类型兼容(例如 Object
,它始终兼容),则除第二个和第三个示例之外的所有示例也适用于动态计算的常量。
由于动态计算的常量可以作为静态参数提供给引导程序方法,因此对引导程序参数的类型没有限制。但是,boolean
、byte
、short
或 char
类型的参数不能由 CONSTANT_Integer
常量池条目 directly 提供,因为 asType
转换不执行必要的缩小原始转换。
在上面的示例中,返回类型始终是 CallSite
,但这不是引导方法的必要特征。在动态计算调用站点的情况下,唯一的要求是引导方法的返回类型必须可转换(使用 asType
转换)到 CallSite
,这意味着引导方法返回类型可能是 Object
或 ConstantCallSite
。在动态解析常量的情况下,引导程序方法的返回类型必须可转换为常量的类型,由其字段类型描述符表示。例如,如果动态常量的字段类型描述符为 "C"
(char
),则引导方法返回类型可以是 Object
、Character
或 char
,但不能是 int
或 Integer
。
- 自从:
- 1.7
-
类描述动态计算常量的引导方法。
ConstantCallSite
是一个CallSite
,其目标是永久性的,永远无法更改。LambdaConversionException促进创建简单“函数对象”的方法,这些函数对象通过委托给提供的MethodHandle
来实现一个或多个接口,可能在类型适配和参数的部分评估之后。方法句柄是对底层方法、构造方法、字段或类似低级操作的类型化、直接可执行引用,具有参数或返回值的可选转换。通过将直接方法句柄分解为其组成的符号部分而获得的符号引用。此类专门由静态方法组成,这些静态方法有助于使方法句柄适应其他 JVM 类型,例如接口。此类仅包含对方法句柄进行操作或返回方法句柄的静态方法。lookup object 是创建方法句柄的工厂,当创建需要访问检查时。一组类选项,指定由Lookup::defineHiddenClass
方法创建的隐藏类是否作为新成员动态添加到查找类的嵌套和/或隐藏类是否与标记为其定义加载器的类加载器有很强的关系。方法类型表示方法句柄接受和返回的参数和返回类型,或者方法句柄调用者传递和期望的参数和返回类型。MutableCallSite
是一个CallSite
,其目标变量的行为类似于普通字段。lambda 表达式的序列化形式。当违反链接不变量时,StringConcatFactory
会抛出 StringConcatException。有助于创建字符串连接方法的方法,可用于有效地连接已知数量的已知类型的参数,可能在类型适应和参数的部分评估之后。SwitchPoint
是一个可以向其他线程发布状态转换的对象。具有类型描述符的实体。TypeDescriptor.OfField <F 扩展 TypeDescriptor.OfField <F>>具有字段类型描述符的实体。具有方法类型描述符的实体符合 JVMS 4.3.3 的方法描述符可以名义上通过MethodType::describeConstable
进行描述;否则它们不能被名义上描述。VarHandle 是对变量或参数定义的变量族的动态强类型引用,包括静态字段、非静态字段、数组元素或堆外数据结构的组件。一组访问模式,指定如何访问 VarHandle 引用的变量。VolatileCallSite
是一个CallSite
,其目标就像一个易变变量。抛出表示代码试图通过错误的方法类型调用方法句柄。