On Thu, 27 Aug 2026 11:01:13 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 memory pool or a local arena 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 eight (four by default) lazily allocated
>> pools per Thread, encoded in `Thread.FieldHolder.confinedMemoryPool`.
>> Virtual threads: Works in the same way but uses its _carrier thread's _
>> cache instead.
>>
>> 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.
>>
>> A confined arena has access to one, two, four, or eight (four by default)
>> platform/carrier thread pools, each of size 64 bytes by default. The pool
>> sizes are configurable via an internal unsupported system property and can
>> be 8 bytes .. 1 MiB. Pooling can also be turned off completely by setting
>> the pool properties to negative values. As there can be up to eight pools
>> per thread, nested confined arenas backed by thread-cached pools are
>> supported (i.e., up to eight 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 a...
>
> Per Minborg has updated the pull request incrementally with one additional
> commit since the last revision:
>
> Check both Exception and Error in test
src/java.base/share/classes/jdk/internal/foreign/ConfinedSegmentPool.java line
130:
> 128: static long acquire(Thread thread) {
> 129: assert thread == Thread.currentThread();
> 130: return POOLING_DISABLED ? 0 :
> acquireFromCache(cacheOwner(thread));
It would be safer to pin a virtual thread to these carrier here (same thing in
release) to ensure there isn't preemption (it's hard to spot the places where
preemption may happen).
if (Thread.currentThread().isVirtual() && ContinuationSupport.isSupported()) {
Continuation.pin();
try {
return acquireFromCache(JLA.currentCarrierThread());
} finally {
Continuation.pin();
}
}
-------------
PR Review Comment: https://git.openjdk.org/jdk/pull/31365#discussion_r3873108415