On Thu, 25 Jun 2026 10:22:22 GMT, Per Minborg <[email protected]> wrote:
>> ## Summary
>>
>> This PR proposes to introduce a pooled confined arena as an optimization for
>> `Arena.ofConfined()`, where small native allocations can be served from a
>> reusable per-thread/per-slot memory pool instead of calling the regular
>> native allocator for every short-lived arena. The arena remains confined to
>> its owner thread and is still closed normally, but its backing storage can
>> be reset and reused when the arena closes. The feature requires no API
>> changes.
>>
>> ### Outline
>>
>> Platform threads: There are up to four lazily allocated pools per Thread,
>> encoded in `Thread.confinedMemoryPool`.
>> Virtual threads: fixed shared native pool with CAS-protected slots, because
>> per-virtual-thread native pools would not scale.
>>
>> Pooled memory is zeroed out upon _closing_ an Arena to minimize data
>> visibility between reuse. This means the data is visible only within a TWR
>> block, and never outside it.
>>
>> By default, a confined arena has access to four pools, each of size 64
>> bytes. The pool sizes are configurable via a system property and can be 8,
>> 16, 32, or 64 bytes. Pooling can also be turned off completely by setting
>> the pool power-of-two size to zero. As there can be up to four pools per
>> thread, nested confined arenas are supported (i.e., up to four nested
>> arenas).
>>
>> ## Static Analysis
>>
>> An extensive static corpus analysis of third-party libraries and the JDK
>> itself has been conducted with respect to `Area.ofConfined()` usage,
>> revealing that confined arenas were used _only_ in TWR blocks and _never_ in
>> an unstructured way. The static analysis further revealed that in most
>> cases, only a small amount of native memory was ever allocated, usually less
>> than 32 bytes, and in many cases, 8 bytes or less. This usage pattern lends
>> itself well to pooling.
>>
>> ## Dynamic Analysis
>>
>> A dynamic statistical analysis of actual runs was also made, where various
>> properties of confined arenas were recorded and summarized during a complete
>> tier1 test run. While a tier1 run is not necessarily representative of a
>> typical application workload, it provided some interesting results:
>>
>> The run produced 93 per-process histogram blocks and 788,773,092 closed
>> confined arenas. The result is dominated by arenas with no native allocation
>> at all: 375,934,768 arenas (47.661%) are in the zero-byte bucket. Counting
>> arenas up to 63 bytes covers 99.997% of all arena closures.
>>
>> The largest count bucket is 8-15 bytes per arena with 400,951,293 arenas
>> (50.832% of all arenas...
>
> Per Minborg has updated the pull request incrementally with two additional
> commits since the last revision:
>
> - Add local pools
> - Add 4 nested levels
I still think we should have a single pool implementation, not the current
split for platform/virtual threads.
Just to recap what we discussed offline:
1. When first allocating, an arena will try to grab a pool from the free list
of the current thread
2. If none is available, allocate a new pool and use that instead
3. When closing the arena, the pool is put in the free list of the current
thread
4. If the free list is full, free the pool immediately
5. When a platform thread exits, free all pools in the free list
For virtual threads, we can just delegate the to current carrier thread (under
the carrier thread access lock) when acquiring/releasing. In this model it
doesn't matter if a vthread is moved to another carrier thread while holding a
pool, since the pool ownership is transferred to the arena when acquiring. i.e.
it's fine if a pool is acquired on one carrier thread, and released on another.
src/java.base/share/classes/jdk/internal/foreign/ConfinedSegmentPool.java line
182:
> 180: final long pool = pools[i];
> 181: if (pool > 0) {
> 182: pools[i] = -pool;
This still looks like we're acquiring pools. I think when an arena grabs a pool
from the free list, the free list slot should be freed up for another (more
nested) arena to put a pool in.
src/java.base/share/classes/jdk/internal/foreign/ConfinedSegmentPool.java line
304:
> 302: * A shared confined segment pool for virtual threads.
> 303: */
> 304: private static final class VirtualThreadPool {
With these latest changes, we should be able to remove the separate pool for
virtual threads, and just do the caching on the carrier thread of the virtual
thread. This would simplify the implementation a lot.
-------------
PR Review: https://git.openjdk.org/jdk/pull/31365#pullrequestreview-4610059510
PR Review Comment: https://git.openjdk.org/jdk/pull/31365#discussion_r3507248876
PR Review Comment: https://git.openjdk.org/jdk/pull/31365#discussion_r3507254418