Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When does JVM decide to reuse old lambda?

Consider the following code fragment:

public static Object o = new Object();

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

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

Method x2() will always return the same lamba object, while x1() will always create new one:

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

will printout something like this:

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

Where (in JVM specification I guess?) is this rule of lambda reuse described? How does JVM decide where do reuse or not?

like image 754
Andremoniy Avatar asked Apr 16 '15 12:04

Andremoniy


People also ask

Can we reuse lambda expression?

Fortunately, you can assign lambda expressions to variables and reuse them, as you would with objects.

How does lambda work in JVM?

For Lambda expressions, the compiler doesn't translate them into something which is already understood by JVM. Lambda syntax that is written by the developer is desugared into JVM level instructions generated during compilation, which means the actual responsibility of constructing lambda is deferred to runtime.

How is lambda expression represented by JVM at runtime?

Lambda expression is compiled using invokedynamic bytecode. Lambda implementation is stored in the same class file as a special private method. Whether an object is created to invoke lambda depends on the situation. In the trivial cases lambda gets translated to a constant method handle.

Why are lambda expressions being added to Java?

Lambda expression is a new and important feature of Java which was included in Java SE 8. It provides a clear and concise way to represent one method interface using an expression. It is very useful in collection library. It helps to iterate, filter and extract data from collection.


3 Answers

You can't be sure about the identity of the object returned for a lambda expression. It can be a new instance, or a pre-existing instance.

This is specified in JLS §15.27.4:

At run time, evaluation of a lambda expression is similar to evaluation of a class instance creation expression, insofar as normal completion produces a reference to an object. Evaluation of a lambda expression is distinct from execution of the lambda body.

Either a new instance of a class with the properties below is allocated and initialized, or an existing instance of a class with the properties below is referenced. If a new instance is to be created, but there is insufficient space to allocate the object, evaluation of the lambda expression completes abruptly by throwing an OutOfMemoryError.

like image 164
Rohit Jain Avatar answered Dec 21 '22 15:12

Rohit Jain


After some investigations, it looks like it depends on the fact that the creation of lambda expressions is performed through invokedynamic and what you see is a side-effect of how invokedynamic behaves on the Oracle's JVM.

Decompiling your x1() and x2() methods:

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

Relevant section of the Constant pool:

 #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;

As explained here:

Because each invokedynamic instruction links (in general) to a different call site (we have two call sites, one for each xN function), the constant pool cache must contain a separate entry for each invokedynamic instruction. (Other invoke instructions can share CP cache entries, if they use the same symbolic reference in the constant pool.)

A Constant Pool cache entry ("CPCE"), when resolved, has one or two words of metadata and/or offset information.

For invokedynamic, a resolved CPCE contains a Method* pointer to a concrete adapter method providing the exact behavior of the call. There is also a reference parameter associated with the call site called the appendix, which is stored in the resolved_references array for the CPCE.

The method is called an adapter because (generally speaking) it shuffles arguments, extracts a target method handle from the call site, and invokes the method handle.

The extra reference parameter is called the appendix because it is appended to the argument list when the invokedynamic instruction is executed.

Typically the appendix is the CallSite reference produced by the bootstrap method, but the JVM does not care about this. As long as the adapter method in the CPCE knows what to do with the appendix stored with the CPCE, all is well.

As a corner case, if the appendix value is null, it is not pushed at all, and the adapter method must not expect the extra argument. The adapter method in this case could be a permanently linked reference to a static method with a signature consistent with the invokedynamic instruction. This would in effect turn the invokedynamic into a simple invokestatic. Many other such strength reduction optimizations are possible.

I'm interpreting that "This would in effect turn" as meaning that in such circumstances (adapter with no parameters) the invokedynamic will effectively behave like and invokestatic call and that the adapter will be cached and reused.

All this is specific to the Oracle's JVM, but i suspect that regarding to this aspect, this one is the most obvious choice and i'd expect to see something like this even in other jvm implementations.

Also, check this good answer for a cleaner rephrasing of that quote, way better than how i'd be able to explain it.

like image 38
uraimo Avatar answered Dec 21 '22 16:12

uraimo


As it was already pointed out, the actual behavior is not specified by the JLS, a future version is allowed to derive from the current implementation as long as the JLS remains fullfilled.

Here is what happens in a current version of HotSpot:

Any lambda expression is bound via an invokedynamic call site. This call site requests a bootstrap method to bind a factory for an instance that implements the functional interface of the lambda expression. As arguments, any variables that are required to execute the lambda expression are handed to the factory. The body of the lambda expression is instead copied into a method inside of the class.

For your example, the desuggared version would look like the following code snipped with the invokedynamic instruction in angle brackets:

class Foo {
  public static Object o = new Object();

  public static Callable x1() {
    Object x = o;
    return Bootstrap.<makeCallable>(x);
  }

  private static Object lambda$x1(Object x) { return x; }

  public static Callable x2() {
    return Bootstrap.<makeCallable>();
  }

  private static void lambda$x2() { return Foo.o; }
}

The boostrap method (which is actually located in java.lang.invoke.LambdaMetafactory) is then asked to bind the call site on its first invocation. For lambda expressions, this binding will never change, the bootstrap method is therefore only called once. In order to being able to bind a class that implements the functional interface, the bootstrap method must first create a class at runtime which look like the following for example:

class Lambda$x1 implements Callable {
  private static Callable make(Object x) { return new Lambda$x1(x); }
  private final Object x; // constructor omitted
  @Override public Object call() { return x; }
}

class Lambda$x2 implements Callable {
  @Override public Object call() { return Foo.o; } 
}

After creating these classes, the invokedynamic instruction is bound to invoke the factory method that is defined by the first class to the call site. For the second class, no factory is created as the class is fully stateless. Therefore, the bootstrap method creates a singleton instance of the class and binds the instance directly to the call site (using a constant MethodHandle).

In order to invoke static methods from another class, an anonymous class loader is used for loading the lambda classes. If you want to know more, I recently summarized my findings on lambda expressions.

But again, always code against the spec, not the implementation. This can change!

like image 26
Rafael Winterhalter Avatar answered Dec 21 '22 15:12

Rafael Winterhalter