我正在寻求澄清
invokedynamic
指令如何在 Java 字节码中运行。为了说明这一点,我准备了一个利用 lambda 表达式的简单 Java 代码片段,这通常会导致在编译时生成 invokedynamic
指令:
public class App{
public static void main(String... args){
Runnable r = () -> {};
r.run();
}
}
使用
javac
编译后,会生成以下字节码:
0: invokedynamic #7, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable;
5: astore_1
6: aload_1
7: invokeinterface #11, 1 // InterfaceMethod java/lang/Runnable.run:()V
12: return
据我了解,在
invokedynamic
指令之后,操作数堆栈应包含 CallSite
(引导方法的结果),随后将其存储到操作数堆栈中。然后,将这个CallSite
存储到变量中并执行。
但是,
Wikipedia上对
invokedynamic
方法的描述指出:
[调用动态指令]调用动态方法并将结果放入堆栈(可能为空);
这个说法,特别是“可能无效”让我很困惑。这是否意味着引导方法不一定会返回
CallSite
?尽管我尝试过,但我无法找到任何生成 invokedynamic
指令而不在操作数堆栈上放置任何值的 Java 代码片段。
有人可以澄清一下
invokedynamic
指令是否可以不将任何东西放入操作数堆栈上,包括CallSite
?我已经查看了 StackOverflow 上的相关答案,例如 this、this 和 this,但没有一个明确解决操作数堆栈的状态。此外,我试图找到一个字节码调试器来检查操作数堆栈状态,但无济于事。
有关此事的任何见解或指导将不胜感激。
您误解了
invokedynamic
的作用。
invokedynamic
不会将 CallSite
推入堆栈。 invokedynamic
指令的操作数指的是返回
CallSite
的方法。对于 lambda 表达式,这是 LambdaMetafactory.metafactory
方法。
invokedynamic
调用此 CallSite
返回方法 (metafactory
),获取 CallSite
,然后调用 CallSite
指示的目标方法句柄。如果此方法句柄需要参数,则会弹出操作数堆栈上的内容。最后,调用该方法句柄的结果被压入堆栈。
在这种情况下,
metafactory
返回的调用站点引用一些不带参数的方法,并返回Runnable
的实现。 invokedynamic
将这个 Runnable
推入堆栈。然后将其存储到本地变量中,从同一变量加载(astore
、aload
),并使用 run
调用其 invokeinterface
方法。
metafactory
需要一些参数,您可能想知道invokedynamic
如何知道要传递哪些参数。其中一部分只是“内置”到 invokedynamic
,其余部分存储在类文件的 BootstrapMethods
属性中。
另一个例子,字符串连接也使用
invokedynamic
。这次,操作数将引用StringConcatFactory
中的方法之一。该方法将返回一个 CallSite
表示某个将返回连接字符串的方法。
有关更多信息,请参阅 JVMS。