Package java.lang.invoke
包提供了与Java虚拟机交互的低级基元。
正如Java虚拟机规范中所述,该包中的某些类型受到虚拟机的特殊处理:
- 类
MethodHandle
和VarHandle
包含签名多态方法,可以链接而不考虑它们的类型描述符。通常,方法链接需要精确匹配类型描述符。
- JVM字节码格式支持类
MethodHandle
和MethodType
的立即常量。
invokedynamic
指令利用引导MethodHandle
常量动态解析CallSite
对象,以实现自定义方法调用行为。
ldc
指令利用引导MethodHandle
常量动态解析自定义常量值。
调用站点和常量的动态解析
以下低级信息总结了Java虚拟机规范的相关部分。有关完整详情,请参阅该规范的当前版本。
动态计算的调用站点
一个invokedynamic
指令最初处于未链接状态。在此状态下,指令没有要调用的目标方法。
在JVM可以执行invokedynamic
指令之前,必须首先对指令进行链接。链接是通过调用一个引导方法来完成的,该方法给出调用的静态信息内容,并且必须生成一个给出调用行为的CallSite
。
每个invokedynamic
指令静态指定自己的引导方法作为常量池引用。常量池引用还指定了调用的名称和方法类型描述符,就像invokestatic
和其他调用指令一样。
动态计算的常量
常量池中可能包含标记为CONSTANT_Dynamic
的常量,配备执行其解析的引导方法。这样的动态常量最初处于未解析状态。在JVM可以使用动态计算的常量之前,必须首先对其进行解析。动态计算的常量解析是通过调用一个引导方法来完成的,该方法给出常量的静态信息内容,并且必须生成常量的静态声明类型的值。
每个动态计算的常量静态指定自己的引导方法作为常量池引用。常量池引用还指定了常量的名称和字段类型描述符,就像getstatic
和其他字段引用指令一样。 (粗略地说,动态计算的常量类似于动态计算的调用站点,就像CONSTANT_Fieldref
类似于CONSTANT_Methodref
。)
引导方法的执行
解析动态计算的调用站点或常量始于解析常量池中以下项目的常量:
- 引导方法,一个
CONSTANT_MethodHandle
- 从
CONSTANT_NameAndType
描述符的类型组件派生的Class
或MethodType
- 静态参数(如果有)(请注意,静态参数本身可以是动态计算的常量)
然后调用引导方法,就像通过MethodHandle.invoke
那样,使用以下参数:
- 一个
MethodHandles.Lookup
,它是在动态计算的常量或调用站点所在的调用类上的查找对象
- 一个
String
,在CONSTANT_NameAndType
中提到的名称
- 一个
MethodType
或Class
,CONSTANT_NameAndType
的解析类型描述符
- 一个
Class
,如果是动态常量,则是常量的解析类型描述符
- 额外的已解析静态参数(如果有)
对于动态计算的调用站点,返回的结果必须是非空引用到CallSite
。调用站点的目标类型必须与从调用的类型描述符派生并传递给引导方法的类型完全相等。如果不满足这些条件,则会抛出BootstrapMethodError
。成功后,调用站点将永久链接到invokedynamic
指令。
对于动态计算的常量,引导方法的第一个参数必须可分配给MethodHandles.Lookup
。如果不满足此条件,则会抛出BootstrapMethodError
。成功后,引导方法的结果将缓存为已解析的常量值。
如果在执行引导方法期间发生异常,例如E
,则解析失败并异常终止。如果E
的类型是Error
或其子类,则重新抛出E
,否则会抛出包装E
的BootstrapMethodError
。如果发生这种情况,则对所有后续尝试执行invokedynamic
指令或加载动态计算的常量都将抛出相同的错误。
解析的时机
一个invokedynamic
指令在其首次执行之前被链接。动态计算的常量在首次使用之前解析(通过将其推送到堆栈或将其链接为引导方法参数)。实现链接的引导方法调用发生在尝试首次执行或首次使用的线程中。
如果存在多个这样的线程,则引导方法可能在多个线程中同时调用。因此,访问全局应用程序数据的引导方法必须采取防止竞态条件的常规预防措施。无论如何,每个invokedynamic
指令要么未链接,要么链接到唯一的CallSite
对象。
在需要具有各自可变行为的invokedynamic
指令的应用程序中,它们的引导方法应生成不同的CallSite
对象,每个链接请求一个。或者,应用程序可以将单个CallSite
对象链接到多个invokedynamic
指令,这样目标方法的更改将在每个指令中可见。
如果多个线程同时为单个动态计算的调用站点或常量执行引导方法,则JVM必须选择一个引导方法结果并将其可见地安装到所有线程中。其他引导方法调用允许完成,但它们的结果将被忽略。
讨论:这些规则不允许JVM共享调用站点,也不允许发出“无因”的引导方法调用。每个invokedynamic
指令最多从未链接转换为链接状态,就在其首次调用之前。没有办法撤消已完成引导方法调用的效果。
引导方法的类型
对于动态计算的调用站点,引导方法使用参数类型MethodHandles.Lookup
,String
,MethodType
,以及任何静态参数的类型;返回类型是CallSite
。
对于动态计算的常量,引导方法使用参数类型MethodHandles.Lookup
,String
,Class
,以及任何静态参数的类型;返回类型是由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)
最后一个示例假设额外的参数是 String
和 Integer
(或 int
)。倒数第二个示例假设所有额外的参数都是 String
类型。其他示例适用于所有类型的额外参数。请注意,除了第二和第三个示例外,如果返回类型更改为与常量的声明类型兼容(例如 Object
,始终兼容),所有示例也适用于动态计算的常量。
由于动态计算的常量可以作为静态参数提供给引导方法,因此对引导参数的类型没有限制。但是,类型为 boolean
、byte
、short
或 char
的参数不能通过 CONSTANT_Integer
常量池条目直接提供,因为 asType
转换不执行必要的缩小原始类型转换。
在上述示例中,返回类型始终为 CallSite
,但这不是引导方法的必要特征。对于动态计算的调用站点,唯一要求是引导方法的返回类型必须可转换(使用 asType
转换)为 CallSite
,这意味着引导方法的返回类型可能是 Object
或 ConstantCallSite
。对于动态解析的常量,引导方法的返回类型必须可转换为常量的类型,表示为其字段类型描述符。例如,如果动态常量具有字段类型描述符为 "C"
(char
),则引导方法的返回类型可以是 Object
、Character
或 char
,但不能是 int
或 Integer
。
- 自Java版本:
- 1.7
-
ClassDescription用于动态计算常量的引导方法。
ConstantCallSite
是一个CallSite
,其目标是永久的,永远不会改变。LambdaConversionException用于简化创建实现一个或多个接口的简单“函数对象”的方法,通过委托给提供的MethodHandle
,可能在类型适配和部分参数评估后进行。方法句柄是对底层方法、构造函数、字段或类似低级操作的有类型、可直接执行的引用,可选地对参数或返回值进行转换。通过将直接方法句柄解析为其组成符号部分而获得的符号引用。该类仅包含帮助将方法句柄适应其他JVM类型(如接口)的静态方法。该类仅包含在方法句柄上操作或返回方法句柄的静态方法。一个查找对象是一个用于创建方法句柄的工厂,当创建需要访问检查时使用。指定由Lookup::defineHiddenClass
方法创建的隐藏类是否动态添加为查找类的新成员以及隐藏类是否与标记为其定义加载器的类加载器具有强关系的一组类选项。方法类型表示方法句柄接受和返回的参数和返回类型,或方法句柄调用者传递和期望的参数和返回类型。MutableCallSite
是一个其目标变量行为类似普通字段的CallSite
。Lambda表达式的序列化形式。当违反链接不变性时,StringConcatFactory
会抛出StringConcatException。用于简化创建字符串连接方法的方法,可用于有效地连接已知数量和已知类型的参数,可能在类型适配和部分参数评估后进行。SwitchPoint
是一个对象,可以向其他线程发布状态转换。具有类型描述符的实体。TypeDescriptor.OfField<F extends TypeDescriptor.OfField<F>>具有字段类型描述符的实体。具有方法类型描述符的实体。符合JVMS 4.3.3的方法描述符可以通过MethodType::describeConstable
进行命名描述;否则,它们无法进行命名描述。VarHandle是对变量或参数化定义的变量族(包括静态字段、非静态字段、数组元素或堆外数据结构的组件)的动态强类型引用。指定由VarHandle引用的变量如何被访问的一组访问模式。VolatileCallSite
是一个其目标行为类似volatile变量的CallSite
。表示代码尝试通过错误的方法类型调用方法句柄时抛出的异常。