Reputation: 34900
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 a new one:
System.out.println(x1());
System.out.println(x1());
System.out.println(x2());
System.out.println(x2());
It 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 the JVM specification I suppose) is this rule of the lambda reuse described? How does JVM decide when to reuse or not?
Upvotes: 22
Views: 1697
Reputation: 44042
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!
Upvotes: 5
Reputation: 1845
There is no way for the compiler to optimise x1()
to return the same lambda -- the behaviour would then be different. Since o
is not final, the lambda returned needs to capture the state of that field (with the x
variable) as its value could change between calling x1()
and invoking the returned lambda.
That's not to say that there aren't situations where the compiler theoretically could reuse the instance but doesn't (the other answers give some insight into that) -- only that this isn't one of those cases.
Upvotes: 0
Reputation: 19821
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.
Upvotes: 6
Reputation: 4209
(Edited this as my previous answer was rubbish!)
This document http://cr.openjdk.java.net/~briangoetz/lambda/lambda-translation.html provides an explanation.
These sections from that document should help answer your question...
Desugaring example -- "stateless" lambdas
The simplest form of lambda expression to translate is one that captures no state from its enclosing scope (a stateless lambda):
...and...
Desugaring example -- lambdas capturing immutable values
The other form of lambda expression involves capture of enclosing final (or effectively final) local variables, and/or fields from enclosing instances (which we can treat as capture of the final enclosing this reference).
Your second method (x2) is an example of the first type of lamba (a stateless one that captures no state from it's enclosing scope) and this is probably why the same lamba is returned in each case.
If you use javap to print out the generated bytecode, you can also see that there is a difference between the two blocks generated...
>javap -p -c L2.class
public class L2 {
public static java.lang.Object o;
public L2();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static java.util.concurrent.Callable<java.lang.Object> x1();
Code:
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<java.lang.Object> x2();
Code:
0: invokedynamic #4, 0 // InvokeDynamic #1:call:()Ljava/util/concurrent/Callable;
5: areturn
private static java.lang.Object lambda$x2$1() throws java.lang.Exception;
Code:
0: getstatic #2 // Field o:Ljava/lang/Object;
3: areturn
private static java.lang.Object lambda$x1$0(java.lang.Object) throws java.lang.Exception;
Code:
0: aload_0
1: areturn
static {};
Code:
0: new #5 // class java/lang/Object
3: dup
4: invokespecial #1 // Method java/lang/Object."<init>":()V
7: putstatic #2 // Field o:Ljava/lang/Object;
10: return
}
Upvotes: 3
Reputation: 213311
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.
Upvotes: 11