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 <[email protected]>
Date: Wednesday, May 29, 2024 at 8:28 PM
To: Chen Liang <[email protected]>
Cc: [email protected] <[email protected]>
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
<[email protected]<mailto:[email protected]>> 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 <[email protected]<mailto:[email protected]>>
Date: Wednesday, May 29, 2024 at 2:43 PM
To: Zhengyu Gu <[email protected]<mailto:[email protected]>>
Cc: [email protected]<mailto:[email protected]>
<[email protected]<mailto:[email protected]>>
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
<[email protected]<mailto:[email protected]>> 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