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...@servicenow.com<mailto:zhengyu...@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 <liangchenb...@gmail.com<mailto:liangchenb...@gmail.com>>
Date: Wednesday, May 29, 2024 at 2:43 PM
To: Zhengyu Gu <zhengyu...@servicenow.com<mailto:zhengyu...@servicenow.com>>
Cc: core-libs-dev@openjdk.org<mailto:core-libs-dev@openjdk.org> 
<core-libs-dev@openjdk.org<mailto:core-libs-dev@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...@servicenow.com<mailto:zhengyu...@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@4de8b406
testMethod() called
1: Func =  LambdaFunc$$Lambda/0x00001f80000c4a20@4de8b406
testMethod() called
2: Func =  LambdaFunc$$Lambda/0x00001f80000c4a20@4de8b406
testMethod() called
3: Func =  LambdaFunc$$Lambda/0x00001f80000c4a20@4de8b406
testMethod() called
4: Func =  LambdaFunc$$Lambda/0x00001f80000c4a20@4de8b406
testMethod() called

….

Outside loop1, Func =  LambdaFunc$$Lambda/0x00001f80000c4c58@402f32ff
testMethod() called
Outside loop2 Func = LambdaFunc$$Lambda/0x00001f80000d1000@5ae9a829
testMethod() called
Outside loop3 Func = LambdaFunc$$Lambda/0x00001f80000d1238@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



Reply via email to