Integrated: 8292697: ZGC: Hangs when almost out of metaspace memory
Stefan Karlsson
stefank at openjdk.org
Fri Aug 26 13:47:29 UTC 2022
On Tue, 23 Aug 2022 15:56:54 GMT, Stefan Karlsson <stefank at openjdk.org> wrote:
> HotSpot performs "critical metaspace allocations" when it's running out of metaspace. The failed allocations are registered in a queue, which the GC prioritizes when cleaning up the metaspace. There's a race in the code that handles these requests.
>
> These requests are added to the queue, and the GC will "process" each request in turn when it runs the metaspace purge phase. The queue handling has an optimization that says that only the first request in the queue needs to trigger the GC, all subsequent requests in the queue will wait for that GC. When the GC gets to the purge phase it will mark in all requests that they have been processed. Note: that this doesn't mean that the request was satisfied, it could be that the result was NULL (and the thread will trigger a last-effort GC before it throws an OOME).
>
> The bug is in the code that determines if a request is responsible for triggering a new GC. The current code just checks if the current request is first in the queue. This doesn't work if the code is called just after the GC has run purge, but before the old requests have been removed. The new request sees that there are already elements in the queue, so it doesn't trigger the GC. And at the same time, the old requests have been processed and they won't trigger the GC either. So, now the new request is waiting for a GC that will not be triggered by anyone.
>
> Note: The reason why there's a delay between the GC processing a request, and the removal from the queue, is that the Java thread that added the request is also responsible for removing the request form the queue. The reason for this is that the mentioned last-effort GC, needs to be able to process the request a second time.
>
> The fix-proposal is to let threads adding new requests check if the added request is the first *non-processed* request in the queue. If it is, that request/thread is responsible for triggering the GC for itself, and any subsequently added requests (until the GC runs the next round of request processing).
>
> However, there's a snatch to this proposal. The request processing is done inside `Metaspace::purge()`, and that function is skipped if the GC didn't unload any classes. The proposed logic relies on that function to always be run when a GC is running. So, I've also changed so that the GC unconditionally calls the request processing. An alternative would be to always run the Metaspace::purge() code. That might even help return memory from temporarily allocated metaspace memory earlier, but I've left that exercise for a potential future improvement.
>
> I've also tweaked the test so that we get a bit more info if this test fails again.
>
> Testing: I could reliably reproduce the original hang on my macbook laptop, within a few minutes. With this fix I can run the test in a loop for hours without reproducing the hang. I've tested this together with Generational ZGC code, running tier1-tier7 on Linux x64. I've started more extensive testing on openjdk/jdk.
This pull request has now been integrated.
Changeset: 3844685b
Author: Stefan Karlsson <stefank at openjdk.org>
URL: https://git.openjdk.org/jdk/commit/3844685be03b121e75095b45a76c0b99f2a41912
Stats: 84 lines in 7 files changed: 51 ins; 5 del; 28 mod
8292697: ZGC: Hangs when almost out of metaspace memory
Reviewed-by: eosterlund, coleenp
-------------
PR: https://git.openjdk.org/jdk/pull/9985
More information about the hotspot-dev
mailing list