经过一些调查,它似乎取决于lambda表达式的创建是通过expterdynamic执行的事实,并且您看到的是expturaldynamic在Oracle的JVM上的行为方式的副作用。
反编译您的 和 方法:x1()
x2()
public static java.util.concurrent.Callable x1();
Code:
stack=1, locals=1, args_size=0
0: getstatic #2 // Field o:Ljava/lang/Object;
3: astore_0
4: aload_0
5: invokedynamic #3, 0 // InvokeDynamic #0:call:(Ljava/lang/Object;)Ljava/util/concurrent/Callable;
10: areturn
public static java.util.concurrent.Callable x2();
Code:
stack=1, locals=0, args_size=0
0: invokedynamic #4, 0 // InvokeDynamic #1:call:()Ljava/util/concurrent/Callable;
5: areturn
常量池的相关部分:
#3 = InvokeDynamic #0:#37 // #0:call:(Ljava/lang/Object;)Ljava/util/concurrent/Callable;
#4 = InvokeDynamic #1:#39 // #1:call:()Ljava/util/concurrent/Callable;
BootstrapMethods:
0: #34 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#35 ()Ljava/lang/Object;
#36 invokestatic Test.lambda$x1$0:(Ljava/lang/Object;)Ljava/lang/Object;
#35 ()Ljava/lang/Object;
1: #34 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#35 ()Ljava/lang/Object;
#38 invokestatic Test.lambda$x2$1:()Ljava/lang/Object;
#35 ()Ljava/lang/Object;
如此处所述:
由于每个调用动态指令(通常)链接到不同的调用站点(我们有两个调用站点,每个 xN 函数一个),因此常量池缓存必须为每个调用动态指令包含一个单独的条目。(如果其他调用指令在常量池中使用相同的符号引用,则可以共享 CP 缓存条目。
常量池缓存条目 (“CPCE”) 在解析后,具有一个或两个字的元数据和/或偏移信息。
对于调用动态,解析的 CPCE 包含一个 Method* 指针,该指针指向一个具体的适配器方法,该方法提供调用的确切行为。还有一个与调用站点关联的引用参数,称为附录,该参数存储在 CPCE 的 resolved_references 数组中。
该方法称为适配器,因为(一般来说)它会随机排列参数,从调用站点中提取目标方法句柄,然后调用方法句柄。
额外的引用参数称为附录,因为它在执行调用动态指令时被追加到参数列表中。
通常,附录是由 bootstrap 方法生成的 CallSite 引用,但 JVM 不关心这一点。只要CPCE中的适配器方法知道如何处理与CPCE一起存储的附录,一切都很好。
作为一个角落情况,如果附录值为 null,则根本不会推送它,并且适配器方法不得期望额外的参数。在这种情况下,适配器方法可以是对静态方法的永久链接引用,其签名与 invokedynamic 指令一致。这实际上会将调用动态转换为简单的调用静态。许多其他这样的强度降低优化是可能的。
我将“这将反过来”解释为在这种情况下(没有参数的适配器),invokedynamic将有效地表现得像和调用静态调用,并且适配器将被缓存和重用。
所有这些都是特定于Oracle的JVM的,但我怀疑在这方面,这是最明显的选择,我希望即使在其他jvm实现中也会看到这样的东西。
另外,检查这个好答案,以便对这句话进行更清晰的改写,这比我能够解释它的方式要好得多。