Question on Lambda function

Zhengyu Gu zhengyu.gu at servicenow.com
Fri May 31 15:18:35 UTC 2024


Thanks a lot, Chen

-Zhengyu


From: Chen Liang <liangchenblue at gmail.com>
Date: Friday, May 31, 2024 at 10:19 AM
To: Zhengyu Gu <zhengyu.gu at servicenow.com>
Cc: core-libs-dev at openjdk.org <core-libs-dev at openjdk.org>
Subject: Re: Question on Lambda function
[External Email]

________________________________
Hi Zhengyu,
This implementation is actually quite good in terms of performance and cost. One improvement you can have is to replace the `invoke` with `invokeExact` (and remember to call MethodHandle.asType() before passing into your anonymous class).

Unfortunately, it will be somewhat slow due to lack of constant folding, as this handle is not stored in a static final but just an instance final field (without -XX:+TrustFinalNonStaticFields VM flag; https://shipilev.net/jvm/anatomy-quarks/17-trust-nonstatic-final-fields/<https://shipilev.net/jvm/anatomy-quarks/17-trust-nonstatic-final-fields>). Therefore, it will be slower than Java 22 MethodHandleProxies (MHP), which uses hidden classes that are automatically trusted.

Your simple implementation is already better than the existing MethodHandleProxies (at least before 22). MHP uses Proxy, which needs to go through InvocationHandler so it had an extra layer of slowness.

Feel free to ask if you have any questions.

Regards,
Chen

On Fri, May 31, 2024 at 9:03 AM Zhengyu Gu <zhengyu.gu at servicenow.com<mailto:zhengyu.gu at servicenow.com>> wrote:
Hi Chen,

Do you see any pros and cons of wrapping a MethodHandle, e.g.

new Function<D, C>() {
  @Override
  public C apply(D t) {
    try {
       return (C) handle.invoke(t);
    } catch (Throwable e) {
      ....
    }
  }


vs using MethodHandleProxies.asInterfaceInstance() ?

I would really appreciate your insights.

Thanks,

-Zhengyu








From: Zhengyu Gu <zhengyu.gu at servicenow.com<mailto:zhengyu.gu at servicenow.com>>
Date: Wednesday, May 29, 2024 at 8:28 PM
To: Chen Liang <liangchenblue at gmail.com<mailto:liangchenblue at gmail.com>>
Cc: core-libs-dev at openjdk.org<mailto:core-libs-dev at openjdk.org> <core-libs-dev at openjdk.org<mailto:core-libs-dev at openjdk.org>>
Subject: Re: Question on Lambda function
Hi Chen,

What is your usage pattern of these single-abstract-method implementations? Since it sounds like you are
creating a lot of them, are you storing them in collections?

Yes, we do have such usage patterns, e.g. stores methods as Function in hash table as handlers, etc.


If you are keeping a lot of them in collection (say, as event handlers), you may try to use `MethodHandleProxies.asInterfaceInstance` as a temporary workaround on JDK 22 and higher (older version uses Proxy, which has horrible invocation performance).

Thanks for the suggestion. We are currently at 17, I will investigate the library.


Best,

-Zhengyu


If you are on older versions from 15 to 21, unfortunately you might have to write a hidden class for the same purpose or use an existing library. One library that might be useful is https://github.com/LanternPowered/Lmbda that effectively generates unloadable hidden classes, but its 3.x builds are not maven central so you have to build yourself.

- Chen

On Wed, May 29, 2024 at 3:35 PM Zhengyu Gu <zhengyu.gu at servicenow.com<mailto:zhengyu.gu at servicenow.com>> wrote:
Hi Chen,

Thanks for the insights.

We did refactor our code to avoid using LambdaMetaFactory,metafactory() directly.

With increasing use of Lambdas, in our applications and libraries, the metaspace impact becomes a concern. If current implementation (not able to unload unused Lambda classes) here to stay, we must come up with a coding guideline to avoid excessive creation of Lambda classes,  any pointers or suggestions would be greatly appreciated.

Best,

-Zhengyu

From: Chen Liang <liangchenblue at gmail.com<mailto:liangchenblue at gmail.com>>
Date: Wednesday, May 29, 2024 at 2:43 PM
To: Zhengyu Gu <zhengyu.gu at servicenow.com<mailto:zhengyu.gu at servicenow.com>>
Cc: core-libs-dev at openjdk.org<mailto:core-libs-dev at openjdk.org> <core-libs-dev at openjdk.org<mailto:core-libs-dev at openjdk.org>>
Subject: Re: Question on Lambda function
[External Email]

________________________________
Hi Gu,
CallSite is specific to each invokedynamic instruction instead of each InvokeDynamic constant pool entry: https://docs.oracle.com/javase/specs/jvms/se22/html/jvms-6.html#jvms-6.5.invokedynamic<https://docs.oracle.com/javase/specs/jvms/se22/html/jvms-6.html#jvms-6.5.invokedynamic>
And the linking is done by MethodHandleNatives.linkCallSite if you want to follow the Java implementation code.
For why the lambda in the loop is constant, it's a feature from InnerClassLambdaMetafactory: https://github.com/openjdk/jdk/blob/c8eea59f508158075382079316cf0990116ff98e/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java#L236
When the lambda is non-capturing, the bootstrap method LambdaMetafactory.metafactory will eagerly create a singleton instance and return this singleton in the indy instruction.

Also, your metaspace pressure might be caused by the fact that Lambda classes (not instances) are no longer eagerly unloaded; see https://github.com/openjdk/jdk/pull/12493 and https://bugs.openjdk.org/browse/JDK-8302154<https://bugs.openjdk.org/browse/JDK-8302154>. You are recommended to create your own facility to create hidden classes in Java 17 instead of continue to use LambdaMetafactory explicitly in code.

Regards,
Chen Liang

On Wed, May 29, 2024 at 12:53 PM Zhengyu Gu <zhengyu.gu at servicenow.com<mailto:zhengyu.gu at servicenow.com>> wrote:
Hello Lambda experts,

Since we upgraded JDK from 11 to 17, we’re experiencing metaspace pressure, largely due to Lambda class implementation changes.

There’s a scenario (see attached test case),  that is especially puzzled me, hopefully, you can share some insights.

In this test case, there is only one Lambda class is created inside the loop, but each one for the same functions outside loop.

Example output:

0: Func =  LambdaFunc$$Lambda/0x00001f80000c4a20 at 4de8b406
testMethod() called
1: Func =  LambdaFunc$$Lambda/0x00001f80000c4a20 at 4de8b406
testMethod() called
2: Func =  LambdaFunc$$Lambda/0x00001f80000c4a20 at 4de8b406
testMethod() called
3: Func =  LambdaFunc$$Lambda/0x00001f80000c4a20 at 4de8b406
testMethod() called
4: Func =  LambdaFunc$$Lambda/0x00001f80000c4a20 at 4de8b406
testMethod() called

….

Outside loop1, Func =  LambdaFunc$$Lambda/0x00001f80000c4c58 at 402f32ff
testMethod() called
Outside loop2 Func = LambdaFunc$$Lambda/0x00001f80000d1000 at 5ae9a829
testMethod() called
Outside loop3 Func = LambdaFunc$$Lambda/0x00001f80000d1238 at 548b7f67
testMethod() called

And jcmd also confirmed there were 4 Lambda classes created:

  49: CLD 0x000060000134cb50: "app" instance of jdk.internal.loader.ClassLoaders$AppClassLoader
      Loaded classes:
         1:    LambdaFunc$$Lambda/0x00001f80000d1238
         2:    LambdaFunc$$Lambda/0x00001f80000d1000
         3:    LambdaFunc$$Lambda/0x00001f80000c4c58
         4:    LambdaFunc$$Lambda/0x00001f80000c4a20
         5:    LambdaFunc

Looking into bytecode, all four call sites have the same invokedynamic bytecode (invokedynamic #7,  0              // InvokeDynamic #0:run:()Ljava/lang/Runnable; ) and the first invokedynamic bytecode is inside the loop.

But when I ran the program with -XX:+TraceBytecodes, it seems that the first invokedynamic was hoisted and result was used in the subsequence loop.

Can anyone explain where this magic happens?  If the magic can apply to the instances outside the loop, so that only one Lambda class is created?


Thank you for your time and expertise,

-Zhengyu



-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/core-libs-dev/attachments/20240531/02679e3f/attachment-0001.htm>


More information about the core-libs-dev mailing list