Question about JEP 371 and unloading Lambdas

Thomas Stüfe thomas.stuefe at gmail.com
Thu Jun 11 07:17:56 UTC 2020


Hi Mandy,

On Wed, Jun 10, 2020 at 7:39 PM Mandy Chung <mandy.chung at oracle.com> wrote:

> Hi Thomas,
>
> Thanks for the data and confirmation.
>
> On 6/9/20 11:19 PM, Thomas Stüfe wrote:
>
>
> :
>
> A reduction in waste from 22% to 4%, mainly due to reduced leftover space
> in half-eaten chunks. Usage drops from 86M->69M.
> But the saved memory is only part of the story. We save a lot of overhead
> too, e.g. less CLDs so smaller CLDG. Also we save storage for the CLDs.
> Also less time spent inside Metaspace critical sections.
>
>
> Thanks for doing the measurement and analysis.  The overhead is reduced
> from 3.74MB to 86.38KB because we no longer create per-VM-anonymous-class
> CLD.
>

That number is just a part of it. It shows the cost of Metachunk headers -
a fixed cost per chunk - and since 20K lambda proxies used 20K CLDs before
your patch, each needing about 2-3 chunks (one class space, one or two from
non-class space) we get ~50-60K chunks:

Total Usage - 20289 loaders, 23276 classes (986 shared):
  Non-Class: 40941 chunks,    64,77 MB capacity,    55,64 MB ( 86%) used,
  6,63 MB ( 10%) free,     3,65 KB ( <1%) waste,     2,50 MB (  4%)
overhead, deallocated: 20504 blocks with 1,24 MB
      Class: 20333 chunks,    21,30 MB capacity,    13,39 MB ( 63%) used,
  6,67 MB ( 31%) free,    16 bytes ( <1%) waste,     1,24 MB (  6%)
overhead, deallocated: 42 blocks with 14,11 KB
       Both: 61274 chunks,    86,07 MB capacity,    69,04 MB ( 80%) used,
 13,29 MB ( 15%) free,     3,66 KB ( <1%) waste,     3,74 MB (  4%)
overhead, deallocated: 20546 blocks with 1,25 MB
                ^^^^^^

With your improvement, we only use one CLD which loads all metadata for all
proxy classes and comes to 1023 chunks:

Usage per loader:

   1: CLD 0x00007f054853cfc0: instance of
de.stuefe.repros.metaspace.internals.InMemoryClassLoader
      Loaded classes:
         1:    ManyManyLambdas1$$Lambda$20245/0x0000000801826160
         2:    ManyManyLambdas1$$Lambda$20244/0x0000000801825f10
         3:    ManyManyLambdas1$$Lambda$20243/0x0000000801825cc0
         4:    ManyManyLambdas1$$Lambda$20242/0x0000000801825a70
         5:    ManyManyLambdas1$$Lambda$20241/0x0000000801825820
.....
      20008:    ManyManyLambdas10$$Lambda$247/0x0000000800cc8040
      20009:    ManyManyLambdas10$$Lambda$246/0x0000000800c79458
      20010:    ManyManyLambdas10$Person
      20011:    ManyManyLambdas1
      20012:    ManyManyLambdas2
      20013:    ManyManyLambdas3
      20014:    ManyManyLambdas4
      20015:    ManyManyLambdas5
      20016:    ManyManyLambdas6
      20017:    ManyManyLambdas7
      20018:    ManyManyLambdas8
      20019:    ManyManyLambdas9
      20020:    ManyManyLambdas10
      -total-: 20020 classes
  Non-Class:  655 chunks,     41,28 MB capacity,    41,22 MB (>99%) used,
 11,58 KB ( <1%) free,     4,02 KB ( <1%) waste,    40,94 KB ( <1%)
overhead, deallocated: 138 blocks with 55,55 KB
      Class:  368 chunks,     11,38 MB capacity,    11,35 MB (>99%) used,
  7,08 KB ( <1%) free,     0 bytes (  0%) waste,    23,00 KB ( <1%)
overhead, deallocated: 367 blocks with 53,19 KB
       Both: 1023 chunks,     52,66 MB capacity,    52,58 MB (>99%) used,
 18,66 KB ( <1%) free,     4,02 KB ( <1%) waste,    63,94 KB ( <1%)
overhead, deallocated: 505 blocks with 108,74 KB
                ^^^^^
which in total gives us 1350 chunks:

Total Usage - 43 loaders, 23285 classes (991 shared):
  Non-Class:  898 chunks,     54,95 MB capacity,    53,94 MB ( 98%) used,
973,28 KB (  2%) free,     4,26 KB ( <1%) waste,    56,12 KB ( <1%)
overhead, deallocated: 355 blocks with 143,06 KB
      Class:  452 chunks,     12,94 MB capacity,    12,79 MB ( 99%) used,
126,36 KB ( <1%) free,    24 bytes ( <1%) waste,    28,25 KB ( <1%)
overhead, deallocated: 407 blocks with 67,60 KB
       Both: 1350 chunks,     67,89 MB capacity,    66,73 MB ( 98%) used,
  1,07 MB (  2%) free,     4,28 KB ( <1%) waste,    84,38 KB ( <1%)
overhead, deallocated: 762 blocks with 210,66 KB
                ^^^^

Comparing raw chunk numbers is misleading since their payload size varies
but it gives an idea of the management overhead.

The biggest saving comes from the fact that these 1023 chunks are fully
used, whereas in the 20K CLD case almost none of the ~60K microchunks were.
This is the "Free in chunks in use" column in the waste section and usually
is the biggest cost when it comes to small class loaders.

If you are interested, the test case is [1], and the measurements are done
with jcmd VM.metaspace.


>
>
> This is a good thing :) Swarms of anonymous classes have always been an
> annoyance for Metaspace since as an arena based allocator 1-class-loaders
> are not exactly its strong side. This has never been a really pressing
> problem for me since the total waste was usually lowish. But it is nice to
> see this gone anyway.
>
> I have two followup questions:
>
> - I do not understand how exactly this relates to Hidden Classes. Could we
> have had this improvement before, by allocating metaspace for anonymous
> classes for lambda proxies via the CLD of the target class? I ask this
> because I want to understand if we can bring this improvement into older
> releases, independent from Hidden Classes.
>
>
> JDK-8239384 [1] gives you the context what changes are made in
> LambdaMetafactory implementation to use hidden class (see JEP 371).  Here
> is a short version:
>
> VM anonymous classes are all weakly linked with their defining loaders to
> allow them going away when it's unused independent of the lifecycle of its
> defining loader.  There is no support for VM anonymous classes to be
> strongly linked with their defining loader.
>
> JEP 371 defines the `Lookup::defineHiddenClass` API to define a hidden
> class which is weakly linked with its defining loader as the default and a
> ClassOption::STRONG [2] can be specified to keep the hidden class strongly
> linked with its defining loader.    Lambda proxy
> class is defined by calling Lookup::defineHiddenClass with
> ClassOption::STRONG option.
>
> Hidden classes are Java SE feature that cannot be backport to older
> releases.
>
>
Thank you for that concise explanation. So, IIUC, when Lambda proxies were
first implemented in jdk8, you needed a class which should be
non-discoverable by the user and the only way to implement that was
anonymous classes? Even though the weak link aspect was not needed for
Lambdas?

>
> - When I was looking at Lambdas, I really was looking for an example case
> with many Hidden Classes whose life time should be quasi random and not
> bound to a single class loader. What you describe in the JEP text under
> unloading. I wanted to look at what this does with Metaspace. But Lambdas
> are a bad example for that, since their lifetimes are still synchronized
> with that of the target class. Is there another example or test case for
> short-lived Hidden Classes?
>
>
> You could create a benchmark simply calling `Lookup::defineHiddenClass` to
> define many hidden classes with and without STRONG option to compare the
> metaspace usage.
>

Thank you, I will try that.


>
> Hope this helps
> Mandy
>
>
Thanks for your help!

..Thomas


> [1]
> https://download.java.net/java/early_access/jdk15/docs/api/java.base/java/lang/invoke/MethodHandles.Lookup.ClassOption.html#STRONG
>
> [2]
> https://bugs.openjdk.java.net/browse/JDK-8239384?focusedCommentId=14326140&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-14326140
>

[1]
https://github.com/tstuefe/ojdk-repros/blob/master/repros8/src/main/java/de/stuefe/repros/metaspace/ManyLambdas.java


More information about the hotspot-runtime-dev mailing list