[jmm-dev] finalization (was: ECOOP, JVMLS)

Hans Boehm boehm at acm.org
Tue Aug 5 18:34:39 UTC 2014


I think a good specific example to think about is a, not entirely
hypothetical, implementation of java.math.BigInteger that just forwards
calls to an underlying native library that actually manages the memory for
the number representations.  The BigInteger finalizer deallocates native
memory.  Currently every BigInteger operation needs a synchronized(this){}
at the end of the method, in order to prevent the underlying native
representation from getting deallocated by a finalizer while the native
arithmetic operation is still running, but the enclosing Java object is
potentially no longer needed.

You may not like this particular example.  But, as far as I can tell, this
is a fairly common pattern.  "Rewrite it in Java" may or may not be good
advice here, but it's certainly not universally applicable.   And it's a
case for which I don't have a good solution except along the lines that
Jeremy proposed.  It's also a case in which premature finalization has
particularly nasty consequences.

Hans


On Tue, Aug 5, 2014 at 12:07 AM, John Rose <john.r.rose at oracle.com> wrote:

> On Aug 4, 2014, at 5:11 PM, Doug Lea <dl at cs.oswego.edu> wrote:
>
> > On 08/03/2014 12:39 PM, Jeremy Manson wrote:
> >
> >> Instead of all of this, let's just make the strong guarantee that if an
> object
> >> is transitively reachable from a local variable that is currently in
> scope
> >> (according to the bytecode's LVT), then the end of that scope happens
> before the
> >> execution of the finalizer.  Maybe we need to make certain guarantees
> about
> >> other GC roots, too (although I'm not sure exactly which - it seems as
> if the
> >> class unloading semantics take care of most of them).  Then we forget
> about this
> >> topic forever.
>
> Here's a dim view of this from the JIT:  Currently the LVT does *not*
> influence execution semantics *at all*; it just helps the debugger.  GC
> root maps are constructed based perhaps on liveness information obtained
> from the bytecodes themselves, but not on any side-attribute.  Making GC
> root maps (and many other code generation details) depend on LVTs would add
> subtle complexity to the program syntax with infrequent benefits.  (What if
> the LVT and bytecodes are contradictory?  Etc.)  If there is a more
> targeted way to get the benefit (e.g., the fence), JITs will be far more
> likely to implement it correctly.
>
> > I like this proposal for the same reasons I like anything that could
> > allow us to get rid of rules that people don't understand, don't
> > follow, and/or don't believe.  (Although it is not as attractive as a
> > proposal to just deprecate method finalize, but we probably could
> > not get away with that.)
>
>
> Just as RAII in C++ allows library writers to protect users from
> neglecting cleanups, try-with-resources in Java can help enforce proper
> cleanup, for some design patterns.  The equivalent in Java to an
> stack-object destructor is the close() method run from the implicit finally
> block of try-with-resources.  That would be a good place to drop an object
> whose lifetime needs stretching.
>
> For example:
>
>   try (MyHeapBlockHolder x = ...) {
>     WeakNativePointer p1 = x.getPtr(1); ... p1.increment(); ...
>   }
>   // finally x.close() => reachabilityFence(x)
>
> The NativePointer types can, if necessary, be arranged to refuse service
> after x.close() runs.  (This is one way I intend to control dangling native
> pointers in Panama APIs.)
>
> What I'm suggesting is a way to make a well-designed API take
> responsibility for the reachability across scopes of use, so the user never
> writes a fence directly.  I don't have any feel for existing APIs that need
> this; I'll bet few of them use try-with-resources, since that feature is
> new.
>
> — John
>
> P.S.  More definitions, in case the argument above seems fuzzy.
>
> A reachability fence is needed when a finalizable object X with a resource
> X.B must be held live across some span of time while operations use that
> resource X.B "weakly".  (The example at the JVMLS was native pointers P1,
> P2 working on a C-heap buffer X.B which is freed by the finalization of a
> header object X, as X.finalize() => free(X.B).  The native pointers P1, P2
> are "weak" relative to X or X.B.)
>
> The common case for a "span of time" is a block execution in a single
> thread.  In that case, we can try to organize the type X so that it only
> allows the user to work with X's weak pointers P1, P2 during a
> try-with-resources (or simple try-finally) on X.
>
>


More information about the jmm-dev mailing list