Avoiding excessive creation of LambdaForm classes

Jesse Schulman jesse at dreamtsoft.com
Fri Jul 28 00:08:41 UTC 2017


We have noticed on a high traffic instance of our application that the
number of loaded classes continues to grow, this in turn increases the
amount of Metaspace and native memory used by the JVM.  Moving forward we
are working on a safe value for MaxMetaspaceSize which should eventually
trigger a GC that cleans up these classes, but we would like to avoid these
classes piling up unnecessarily if possible.

During investigation we found that the large majority of these classes are
LambdaForm that are created as a result of how we convert java Map and List
instances into proper javascript Object and Array instances.

We run with the --no-java option and expose functionality to javascript via
our own implementations of JSObject.  The attached reproducer represents
how we convert java Maps and Lists in order to return them from our
JSObject implementations.

My best guess from investigating is that when we convert a complex java
Map/List that contains many nested Maps/Lists we create many new instances
of javascript Objects/Arrays as seen in the attached reproducer.  It seems
we go thru the Invoker.maybeCustomize every time we call getMember and
every time we call newObject in createNewGlobalObject method of the
reproducer.  Each time it increments the MethodHandle.customizationCount
until that is 127, and then another LambdaForm is created.

I ran the attached reproducer with -verbose:class turned on and a logging
breakpoint in my IDE that logs the mh.customizationCount every time
Invokers.maybeCustomize calls mh.customize().  Once the reproducer logs
"STARTING TEST" and loads some initial classes I see the breakpoint log
that we've hit 127 customizations followed by the loading of another
LambdaForm class:

customize 127
customize 127
[Loaded java.lang.invoke.LambdaForm$BMH/1049817027 from
java.lang.invoke.LambdaForm]
[Loaded java.lang.invoke.LambdaForm$BMH/23211803 from
java.lang.invoke.LambdaForm]
customize 127
[Loaded java.lang.invoke.LambdaForm$BMH/1923598304 from
java.lang.invoke.LambdaForm]
customize 127
[Loaded java.lang.invoke.LambdaForm$BMH/776700275 from
java.lang.invoke.LambdaForm]
customize 127
customize 127
[Loaded java.lang.invoke.LambdaForm$BMH/118394766 from
java.lang.invoke.LambdaForm]
[Loaded java.lang.invoke.LambdaForm$BMH/386163331 from
java.lang.invoke.LambdaForm]
customize 127
[Loaded java.lang.invoke.LambdaForm$BMH/1540374340 from
java.lang.invoke.LambdaForm]


So I have 2 questions...

1. Is there a better/faster way for us to create the proper javascript
Object/Array instances within our java code so we don't trigger these
classes to be loaded?

2. Is this the intended behavior given:
    a. We don't actually evaluate any javascript at all in the reproducer,
I can see generating these classes if we were evaluating a lot of different
javascript
    b. Every time we call getMember against the global bindings we only
ever pass "Array" or "Object" and when calling newObject against the
Array/Object constructors we always do so with zero arguments, meaning we
only call 4 different signatures yet the MethodHandle seems to be
"customized" hundreds of times

My assessment may be off or this may be exactly the expected behavior, but
would still really appreciate any guidance on doing this type of conversion
to avoid loading so many LambdaForm classes.

Thanks!
Jesse


More information about the nashorn-dev mailing list