JDK-8242451 : ensure semantics of non-capturing lambdas are preserved independent of execution mode
  • Type: Enhancement
  • Component: core-libs
  • Sub-Component: java.lang.invoke
  • Affected Version: 11,15,16
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2020-04-09
  • Updated: 2024-11-22
  • Resolved: 2020-09-25
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 16
16 b18Fixed
Related Reports
Relates :  
Relates :  
Description
JDK-8232806 introduced the jdk.internal.lambda.disableEagerInitialization system property to be able to disable eager initialization of lambda classes. This was necessary to prevent side effects of class initializers triggered by such initialization in the context of the GraalVM native image tool.

However, the change as implemented means that the semantics of non-capturing lambdas in native images is different than in JVM execution. That is, the program below would print "true" on the JVM but "false" in a native image:

import java.util.function.Supplier;
public class LambdaIdentity {
    public static void main(String[] args) {
        System.out.println(lambda() == lambda());
    }
    private static Supplier<String> lambda() {
        return () -> "value";
    }
}

As [~briangoetz] correctly states in JDK-8232806, the above program is making assumptions above reference equality that the Java spec does not guarantee.

However, for better or worse there are programs out there that rely on this faulty assumption. Graal itself was one such program. This resulted in subtle and very hard to debug performance issues when using Graal in its libgraal form (i.e. as a native image). We do not wish the same experience on other users of native image and so propose that the implementation of reference equality for non-capturing lambdas should be the same independent of whether executing on the JVM or as a native image. At the very least, this should hold within the context of a single JDK version.

Comments
Changeset: 1b79326c Author: Gilles Duboscq <gdub@openjdk.org> Date: 2020-09-25 10:10:36 +0000 URL: https://git.openjdk.java.net/jdk/commit/1b79326c
25-09-2020

When we make lambda proxies as inline classes (see JDK-8205668), programs depending on the incorrect identity assumption of lambda proxies will be broken.
25-08-2020

Indeed, we expect this case to be rare. This seems a sensible approach, we can revisit the corner case when more data is available.
18-05-2020

The details of why the "impl" MethodHanlde is used are in a comment in JDK-8239384: https://bugs.openjdk.java.net/browse/JDK-8239384?focusedCommentId=14328369&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-14328369 As for why it conflicts with lazy initialization of the lambda class: we have to set a static field on the generated lambda class and we can't do that without initializing the class. In the capturing case we could somehow pass that method handle as an extra argument (MethodHandles#insertArguments) but that wouldn't work for the non-capturing case. This case should be rather rare since it involves binary compatibility against classes that have changed since the initial compilation so it might be fine to ignore. My current thinking is to go for something that works except in this case and then monitor how often we actually hit that case in the wild in a context where re-compilation is not an option.
18-05-2020

[~gdub] Could you provide more detail?
18-05-2020

After the switch to hidden classes in the InnerClassLambdaMetafactory (JDK-8239384), it is hard to get lazy initialization in the case where a "impl" MethodHandle is used. This is used in some rare separate-compilation case.
18-05-2020

[~dnsimon] Understood. I include this comment mostly so that this history is not misinterpreted in the future as either "any implementation detail generates a behavioral compatiblity constraint" or "the implementation is obligated to bend over backwards to accomodate explicitly-broken code"; there's a lot of wishful thinking out there about what compatibility means. The reality here is that we have a behavioral divergence between OpenJDK and GraalVM, which we introduced knowingly in JDK-8232806 (because that was the pragmatic choice at the time), and that it is also OK to go back and refine the behavior to narrow the scope of behavioral divergence now that we have the time to do so (subject to the usual disclaimers of risk management etc.)
09-04-2020

Thanks [~briangoetz]. Indeed, I am not trying to imply any issue of correctness wrt JLS. Just that would be better for GraalVM users if their code worked the same way no matter which engine is used to execute it. Please let me know if/how I can rephrase/improve the description to convey this message more clearly.
09-04-2020

By way of clarification, not only does the JLS not guarantee these, but it goes out of its way to specifically state that such assumptions are unfounded and dangerous. (That said, I don't object to reducing sources of divergence such as those introduced by JDK-8232806 where there is not a clear benefit to said divergence.)
09-04-2020