<!DOCTYPE html><html><head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>
<p>Hi,<br>
thanks for the feedback. The decision of whether to lump or split
various kind of arenas has defintively been on our mind while
designing the API.</p>
<p>First, let me point out that I find your connection to integrity
by default a bit excessive. You can already have memory leaks in
Java in all sort of ways -- without the need to resort to off-heap
memory. A program with a memory leak doesn't run into undefined
behavior, nor does it undermine memory safety. It is just a badly
written (and possibly very inefficient) program. (Note that Rust
has a similar stance [1] -- preventing memory leaks is *not* among
the guarantees provided by the Rust type system).<br>
</p>
<p>More to your point, our experience with trying to provide an API
that more clearly separated between managed and unmanaged lifetime
always led to the feeling that, while more formally correct, more
splitty APIs weren't really improving the situation much -- and,
at the same time, would make discoverability of the API worse, by
scattering arena factories across the FFM API.</p>
<p>Let's say, for argument sake, that we had two Arena types --
Arena and ManagedArena. ManagedArena would have a close method,
while Arena would not. Let's say that, for ease of use, one would
start from a plain Arena:</p>
<p>```<br>
Arena arena = Arena.ofAuto();<br>
arena.allocate(...)<br>
foo(arena);<br>
```</p>
<p>But later on, as you point out, one realizes that more control
over deallocation is required, and a confined arena is needed.
Let's say we change only the first factory call:</p>
<div style="background-color:#ffffff;color:#000000">
<p>```<br>
Arena arena = ManagedArena.ofConfined();<br>
arena.allocate(...)<br>
foo(arena);<br>
```</p>
<p>The rest of the code would still compile perfectly. So, having
a more splitty hierarchy here doesn't really buy a lot of extra
safety, as you can still refactor your code in ways that leaves
deallocation behind. That is, unless you go completely overboard
and make Arena and ManagedArena incompatible, which would be
very bad, as now developers would have to keep two unrelated
abstractions in their minds at all times (meaning the split
would also affect client code) -- we tried this in Java 20 and
it didn't really work out. E.g. an important use case such as
Arena extensibility was badly hurt by the split move, as users
could define custom managed arenas, but they could not define
custom automatic arenas with more efficient allocation policies
(which resulted in tension/friction between Arena and
SegmentAllocator). </p>
<p>Instead of lump vs. split, I think the crucial observation is
this:</p>
<p> </p>
<blockquote type="cite">Because these implementations throw an
exception on <code>close()</code>, developers are actively
incentivized (and practically forced) to avoid the <code>try-with-resources</code>
pattern.</blockquote>
<br>
This has also been the subject of many discussions while designing
the API. There's basically two questions here:
<p>* should close() be idempotent? E.g. should closing an already
closed arena succeed?<br>
* should close() tolerate cases where the method is called on a
managed arena (with the subtext that, in such cases, close() is,
again, a no-op)</p>
<p>When designing the API, we always erred on the side of catching
user bugs -- out of a desire to make a low-level API such as FFM
as deterministic and predictable as possible (e.g. close really
means close). In this light, closing an already closed arena
seems like a bug (double free?) so, throwing an exception seemed
reasonable. And, similarly, closing an arena externally managed
also seemed suspicious, as no real "free" occurs there. Although
I concede that argument for the former is less clear-cut than
the latter -- e.g. even if close were to be idempotent, the
invariant that all segments associated with a closed arena are
no longer accessible would still be valid. On the other hand,
allowing to close a managed arena might create an expectation
that its segments are no longer accessible, whereas that's not
the case.<br>
</p>
<p>There's no "free lunches" here -- either we make the API more
easily usable with try-with-resources _or_ we detect suspicious
activity in user code (such as a double close), but we can't do
both.<br>
</p>
Perhaps, to mitigate some of the concerns you have, some other
avenues could be explored:<br>
<br>
* an IDE analysis could e.g. detect cases where a confined/shared
arena is used w/o a corresponding try-with-resource block<br>
* some JFR event could be triggered when an confined/shared Arena
becomes unrechable and its scope is still alive (Netty's ByteBuf
API has something similar to this [2])<br>
</div>
<div style="background-color:#ffffff;color:#000000"><br>
</div>
<div style="background-color:#ffffff;color:#000000">Summing up,
after staring at this problem for a very long time, I don't think
there's a "perfect" Arena API that is rid of all the
aforementioned issues -- it's mostly a "pick your poison"
situation.<br>
</div>
<p>Cheers<br>
Maurizio<br>
<br>
[1] - <a class="moz-txt-link-freetext" href="https://doc.rust-lang.org/book/ch15-06-reference-cycles.html">https://doc.rust-lang.org/book/ch15-06-reference-cycles.html</a><br>
[2] -
<a class="moz-txt-link-freetext" href="https://netty.io/wiki/reference-counted-objects.html#troubleshooting-buffer-leaks">https://netty.io/wiki/reference-counted-objects.html#troubleshooting-buffer-leaks</a><br>
</p>
<p><br>
</p>
<div class="moz-cite-prefix">On 16/01/2026 23:49, Abraham Tehrani
wrote:<br>
</div>
<blockquote type="cite" cite="mid:CALHZk=nv6ZTXu=-nsY5nUr4eUCesQY_D3keyW04_hWUEAZupog@mail.gmail.com">
<div dir="ltr">
<p>Dear Panama Dev Team,</p>
<p>I would like to offer feedback on the current design of the <code>Arena</code>
interface, specifically the decision to have <code>global()</code>
and <code>ofAuto()</code> throw <code>UnsupportedOperationException</code>
on <code>close()</code>.</p>
<p>While I understand the desire for a unified interface, I
believe this design creates a significant "Refactoring Hazard"
that undermines the goal of <b>Integrity by Default</b>.</p>
<p><b>The Path to a Leak:</b> Most developers will naturally
start with <code>global()</code> or <code>ofAuto()</code>
for ease of use. Because these implementations throw an
exception on <code>close()</code>, developers are actively
incentivized (and practically forced) to avoid the <code>try-with-resources</code>
pattern.</p>
<p>Later, when the developer needs to optimize and switches to <code>ofConfined()</code>
or <code>ofShared()</code>, they will perform a "drop-in"
replacement of the implementation. Because <code>Arena</code>
is a unified interface, the code will compile perfectly.
However, the developer, now conditioned to treat Arenas as
managed, will likely fail to wrap the new implementation in a
<code>try-with-resources</code> block.</p>
<p><b>The Result:</b> A catastrophic off-heap memory leak.</p>
<p>In almost any other Java API, refactoring toward more manual
control is guarded by the compiler or the type system. Here,
the "ease of use" of a single interface effectively masks a
fundamental change in lifecycle responsibility.</p>
<p>When dealing with <i>native memory</i>, <b>correctness and
explicit lifecycle management must take precedence over
interface uniformity.</b> We are trading a slightly "split"
API for a very real "split" in runtime safety.</p>
<p>The easiest way to use an API should also be the safest way.</p>
<p>Respectfully and Humbly,<br>
Abraham Tehrani</p>
</div>
</blockquote>
</body>
</html>