Finalization and dead references: another proposal

Vitaly Davidovich vitalyd at gmail.com
Thu Dec 7 02:27:12 UTC 2017


On Wed, Dec 6, 2017 at 7:38 PM Hans Boehm <hboehm at google.com> wrote:

> We're still trying to deal with a fair amount of code that implicitly
> assumes that finalization or similar clean-up will not occur while a
> pointer to the affected object is in scope. Which is of course not true.
>
> As a reminder, the canonical offending usage (anti-)pattern with
> (deprecated, but easier to write) finalizers is
>
> class Foo {
>     private long mPtrToNativeFoo;
>
>     private static native void nativeDeallocate(long nativePtr);
>     private static native void nativeDoSomething(long nativePtr, long
> anotherNativePtr);
>
>     protected void finalize() { ... nativeDeallocate(mPtrToNativeFoo); ...
> }
>
>     public void doSomething(Foo another) { ...
> nativeDoSomething(mPtrToNativeFoo, another.mPtrToNativeFoo) ... }
>     ...
> }
>
> This is subtly incorrect in that, while executing the final call to
> doSomething() on a particular object, just after retrieving mPtrToNativeFoo
> and another.mPtrToNativeFoo, but before invoking nativeDoSomething(), the
> garbage collector may run, and "this" and "another" may be finalized,
> deallocating the native objects their mPtrToNativeFoos refer to.
> When nativeDoSomething() finally does run, it may see dangling pointers.
>
> Examples using java.lang.ref or Cleaners (or even WeakHashMap, if you must)
> instead of finalize() are as easy to construct, but a bit longer. (The
> finalizer version is also arguably incorrect in other ways that are not
> relevant here. Pretend this were written in terms of PhantomReferences.)
>
> It is easily possible to construct 100% Java code with the same problem.
> Instead of mPtrToNativeFoo, each object stores an integer handle that is
> used to access additional Java state logically associated with the object.
> But the native pointer case seems to dominate in practice.
>
> Various solutions to this have been proposed, but none seem quite
> attractive enough that I actually feel comfortable asking people to update
> their code to use them. Noteworthy proposals include:
>
> 0) Explicitly call close() on such objects. Great idea when it works. In
> general it doesn't, since the code needs to know when the enclosing Java
> object is no longer needed. If we always knew that we wouldn't need a GC.
> 1) Various hacks to keep variables live, e.g. the one based on synchronized
> blocks I advocated in my 2004 JavaOne talk. These are slow and ugly, as
> we've always admitted. Nobody used them. Incorrect won over slow, ugly, and
> complicated ~100% of the time.
> 2) Java 9's reachabilityFence(). This is better. It can be implemented so
> that it's no longer slow. But in many common cases, it's still quite ugly.
> For example, the call to nativeDoSomething() above must be followed by two
> reachabilityFences, one on this and one on another. And to do this really
> correctly, the whole thing would often need to be in a try...finally block.
> And in reality code following this pattern usually doesn't have just a
> single doSomething method that needs this treatment, but may easily have
> dozens. And the rules for placing reachabilityFences can become quite
> subtle if there are e.g. locally allocated objects involved. My assessment
> is that this isn't good enough. People may no longer write incorrect code
> 100% of the time, but I'd bet on 70%+.
> 3) JNI functions can be rewritten, so that the containing Java object is
> passed in addition to the native pointers. Somewhat accidentally, this
> happens to be roughly free for single argument functions. (Delete
> "static".) It adds overhead in other cases, like the one above, and the
> rewriting can get somewhat convoluted. In some cases, it doesn't work at
> all. AFAIK, it's never actually guaranteed to be correct; it works because
> standard implementations don't optimize across the language boundary.
> That's not too likely to change. Maybe.
> 4) We could change the language spec to always prohibit premature
> finalization/cleaning in cases like the above. I could personally live with
> that solution, and proposed it internally here in the past. But it doesn't
> seem to go over well among implementers. And AFAICT, doing it well requires
> significant tooling changes, in that we do want to reliably treat local
> variables as dead once they go out of scope, a piece of information that
> doesn't seem to be reliably preserved in class files. One could argue that
> the current class file design implicitly assumes that we can do dead
> variable elimination.
>
> After going back and forth on this, my conclusion is that we need a
> linguistic mechanism for identifying the case in which the garbage
> collector is being used to managed external resources associated with a
> field.

So kind of the opposite of WeakReference - a SuperStrongReference :).

Kidding aside, it seems like the way you’d want to encapsulate this at the
language level is via a type that the JVM intrinsically knows about; in
this way it’s similar to the reference types today.

An annotation probably does the trick when the value doesn’t escape from
the enclosing instance but I’ve no idea if that assumption covers enough
code to warrant this approach.  AFAICT, if the value escapes into an
instance of another type that doesn’t annotate its field then all bets are
off.

Having a wrapper type would at least make it harder to leak the  native
handle vs the annotation approach.  But of course the wrapper comes with
footprint and indirection costs.  Maybe Valhalla could allow exposing some
magic value type that’s a zero-cost wrapper but preserves the type
information the JIT can track?

> A (still slowly evolving) proposal to add an annotation to do so is
> at
>
>
> https://docs.google.com/document/d/1yMC4VzZobMQTrVAraMy7xBdWPCJ0p7G5r_43yaqsj-s/edit?usp=sharing
>
> In many ways, this is inherently a compromise. But in the vast majority of
> cases, it greatly reduces the required source code changes over all but (4)
> above. And I think I could usually explain to an author of currently broken
> code in under 5 minutes exactly what they need to do to fix it. And I
> wouldn't have to lie much to do so. I don't think (0)-(3) above share that
> property.
>
> This has already benefited from comments provided by a few people. (Thanks
> to Doug, Jeremy, and Martin, among others.) But I would really like more
> feedback, including suggestions about how to proceed.
>
> Hans
>
-- 
Sent from my phone


More information about the core-libs-dev mailing list