JVM何时决定重用旧的lambda?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (58)

考虑下面的代码片段:

public static Object o = new Object();

public static Callable x1() {
    Object x = o;
    return () -> x;
}

public static Callable x2() {
    return () -> o;
}

方法x2()将始终返回相同的lamba对象,同时x1()将始终创建一个新对象:

    System.out.println(x1());
    System.out.println(x1());
    System.out.println(x2());
    System.out.println(x2());

将打印出如下所示的内容:

TestLambda$$Lambda$1/821270929@4a574795
TestLambda$$Lambda$1/821270929@f6f4d33
TestLambda$$Lambda$2/603742814@7adf9f5f
TestLambda$$Lambda$2/603742814@7adf9f5f

在这里(在JVM规范中,我猜?)是否描述了lambda重用的这个规则?JVM如何决定重用或不重用?

提问于
用户回答回答于

你无法确定为lambda表达式返回的对象的身份。它可以是一个新的实例,也可以是一个预先存在的实例。

这在JLS§15.27.4中有规定:

在运行时,lambda表达式的评估与类实例创建表达式的评估类似,只要正常完成产生对对象的引用即可。对lambda表达式的评估与lambda体的执行是不同的。 要么分配具有以下属性的类的新实例并进行初始化,要么引用具有以下属性的类的现有实例。如果要创建新实例,但没有足够的空间分配对象,则通过抛出OutOfMemoryError突变地完成对lambda表达式的评估。

用户回答回答于

经过一番调查,看起来这取决于创建lambda表达式是通过invokedynamic执行的,并且你看到的是invokedynamic在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;

这里所解释的:

由于每个invokedynamic指令都链接到一个不同的调用站点(我们有两个调用站点,每个xN函数一个),常量池缓存必须为每个invokedynamic指令包含一个单独的条目。(其他调用指令可以共享CP高速缓存条目,如果它们在常量池中使用相同的符号引用。) 常量池高速缓存条目(“CPCE”)解析后具有一个或两个单词元数据和/或偏移量信息。 对于invokedynamic,已解析的CPCE包含指向具体适配器方法的Method *指针,以提供调用的确切行为。还有一个与呼叫站点相关的参考参数,称为附录,存储在CPCE的resolved_references数组中。 该方法称为适配器,因为(一般来说)它会混洗参数,从调用站点提取目标方法句柄,并调用方法句柄。 额外的引用参数称为附录,因为它在执行invokedynamic指令时附加到参数列表中。 通常,附录是由引导方法生成的CallSite引用,但JVM不关心这一点。只要CPCE中的适配器方法知道如何处理与CPCE一起存储的附录,一切都很好。 作为一个特例,如果附录值为null,那么它根本不会被推送,并且适配器方法不能期待额外的参数。在这种情况下,适配器方法可以是永久链接的静态方法的引用,其签名与invokedynamic指令一致。这实际上将invokedynamic转变为简单的invokestatic。许多其他的这种强度降低优化是可能的。

所属标签

可能回答问题的人

  • HKC

    红客学院 · 创始人 (已认证)

    26 粉丝7 提问5 回答
  • 西风

    renzha.net · 站长 (已认证)

    9 粉丝1 提问4 回答
  • Dingda

    Dingda · 站长 (已认证)

    4 粉丝0 提问3 回答
  • 螃蟹居

    1 粉丝0 提问2 回答

扫码关注云+社区

领取腾讯云代金券