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

John Rose john.r.rose at oracle.com
Tue Aug 5 07:07:36 UTC 2014

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