Hi Oli, from looking at our invokdedynamic code for a while I realized there are actually two potential bugs in there for this case. I am not yet sure it is related to what you see.
The bug I found is the following: ``` def foo(x) { x.getName() } foo(String) foo(Integer) ``` The callsite in foo is having a Class instance as receiver. The Methodhandle we create for the call checks for the receiver class to stay the same, but the caching mechanism in front of the handle sees here only Class as receiver. So for this cache the receiver does not change and for the handle it does. The result is that for every such call a new handle is produced. To show it with your code example: ``` def foo(Object x) { if (x.getClass().getName().equals("dummy")) print "dummy" } while (true) { foo("1") 127.times { foo(1) } } ``` The foo("1") will produce the first handle for getName in foo. The foo(1) the second, which replaces the handle for foo("1"). Then foo(1) is called 126 times more without replacement. Now the loop repeats and foo("1") produces a new handle, followed by foo(1) creating a new handle that lasts 127 calls till the loop repeats again. None of the handles are reused. The cache supposed to solve this will have at maximum 1 element here. Is it wrong for the handle to have this class check in there? Well, not exactly. In Groovy it is perfectly valid to do def x = Foo x.bar() to call a static method bar on the class Foo. So for static method calls I see a bug in the caching. But in this case it is not a static method call, it is a call on the instance Class, here arguably the handle should not do the class check like it does. If I remove the class receiver guard for testing I see a lot less of those class,init messages bye Jochen On 05.08.23 16:27, Gillespie, Oli wrote:
I have simplified the reproducer slightly, no concurrency is needed. ``` // Reproducer for indy performance/classloading issue. // Run with JAVA_OPTS="-Xlog:class*=info" groovy test.groovy, // and observe a high volume of LambdaForm classes churning // even during steady state. def foo(Object x) { if (x.getClass().getName().equals("dummy")) print "dummy" } while (true) { foo("1") // 127 is just enough to trigger compilation of a new MethodHandle since // java.lang.invoke.MethodHandle.CUSTOMIZE_THRESHOLD defaults to 127. 127.times { foo(1) } } ``` It seems that every invocation where the argument types change causes IndyInterface to generate a brand new MethodHandle for the invocation. The LambdaForm compilation then happens when the new MethodHandle is used enough times to trigger customization. I think some of this was discussed in https://mail.openjdk.org/pipermail/mlvm-dev/2017-May/006760.html, but I don't fully understand how it applies to my case. Amazon Development Centre (London) Ltd. Registered in England and Wales with registration number 04543232 with its registered office at 1 Principal Place, Worship Street, London EC2A 2FA, United Kingdom.