RFR: 8302154: Hidden classes created by LambdaMetaFactory can't be unloaded [v2]
Thomas Stuefe
stuefe at openjdk.org
Fri Feb 17 21:48:33 UTC 2023
On Thu, 9 Feb 2023 18:11:18 GMT, Volker Simonis <simonis at openjdk.org> wrote:
>> Prior to [JDK-8239384](https://bugs.openjdk.org/browse/JDK-8239384)/[JDK-8238358](https://bugs.openjdk.org/browse/JDK-8238358) LambdaMetaFactory has created VM-anonymous classes which could easily be unloaded once they were not referenced any more. Starting with JDK 15 and the new "hidden class" based implementation, this is not the case any more, because the hidden classes will be strongly tied to their defining class loader. If this is the default application class loader, these hidden classes can never be unloaded which can easily lead to Metaspace exhaustion (see the [test case in the JBS issue](https://bugs.openjdk.org/secure/attachment/102601/LambdaClassLeak.java)). This is a regression compared to previous JDK versions which some of our applications have been affected from when migrating to JDK 17.
>>
>> The reason why the newly created hidden classes are strongly linked to their defining class loader is not clear to me. JDK-8239384 mentions it as an "implementation detail":
>>
>>> *4. the lambda proxy class has the strong relationship with the class loader (that will share the VM metaspace for its defining loader - implementation details)*
>>
>> From my current understanding the strong link between a hidden class created by `LambdaMetaFactory` and its defining class loader is not strictly required. In order to prevent potential OOMs and fix the regression compared the JDK 14 and earlier I propose to create these hidden classes without the `STRONG` option.
>>
>> I'll be happy to add the test case as JTreg test to this PR if you think that would be useful.
>
> Volker Simonis has updated the pull request incrementally with two additional commits since the last revision:
>
> - Remove assertions which insist on Lambda proxy classes being strongly linked to their class loader
> - Removed unused import of STRONG und updated copyright year
Hi Volker,
just a short feedback since I'm entering vacation mode.
> In general it seems to me that creating a new `ClassLoaderData` for each hidden (or VM anonymous) class is a lot of overhead and was probably only done as "a hack" to simplify the implementation of class unloading for such classes. This might have been appropriate in the "early days" when we had just a few VM anonymous classes. But as the usage of `LambdaMetafactory.metafactory()` becomes more common it may make sense to revise the implementation such that for example all hidden classes defined by a class loader can live in a single `ClassLoaderData` structure. I think this should be technically possible although it would complicate the unloading logic (and should therefore be postponed to a later change).
Interesting numbers. I underestimated the impact of native allocations.
A side note, the amount of waste avoided by JEP 371 depends highly on the actual size of the lambda (precisely, how close a lambda would be to the chunk size). That's pretty much random, but nice to know since it can shift the numbers from almost nil overhead to almost twofold overhead.
>I'd argue that Metaspace is more "valuable" than native memory because it is limited by -XX:MetaspaceSize, whereas native memory is only limited by the amount of available memory on the system. Also, growing Metaspace will trigger garbage collections which might be unexpected.
I don't follow that logic, for several reasons:
- Metaspace is more easily reclaimed, since it returns memory to the OS eagerly (since JEP 387). C-Heap, otoh, sticks to the process unless you (atm) manually trim. Depends on the libc used, but with glibc - arguable the main platform - this is the state. We are working on C-heap autotrim, but it is not completed yet.
So while Metaspace is limited, it recovers from spikes more easily.
- The metaspace GC threshold is an (impreceise) mechanism to trigger GC based on native resource consumption. That this is not affected by C-heap memory is just a side effect of an imperfect implementation - ideally, all native resources associated with a class loader - be it metaspace or C-heap - together would weight into the decision of when to do a GC. In fact, your numbers may be taken as a hint that maybe CLDs should live in Metaspace instead.
---
As I stated before, we do have the ability to prematurely repurpose metaspace before the loader dies via Metaspace::deallocate(). It would be nice if there were a mechanism to trigger this at least from Java. E.g. by making Lambdas closable.
-------------
PR: https://git.openjdk.org/jdk/pull/12493
More information about the core-libs-dev
mailing list