[jmm-dev] ECOOP, JVMLS

Hans Boehm boehm at acm.org
Mon Aug 4 19:03:58 UTC 2014


I really like this direction, and I'm increasingly convinced that it's the
only sane way to avoid a reasonably large number of bugs.  This problem is
so esoteric, most programmers will never understand the problem, much less
explicitly program around it.  We've been telling people since 2005 that
you need synchronized(this){} all over the place in code with finalizers,
and I have yet to see a single piece of code "in the wild" that actually
does this.  Based on a small unscientific sampling, > 50% of all code using
finalizers is broken in this way.  (The rest tends to do things like log
the fact you let the GC collect something that should have been explicitly
deallocated, without actually deallocating it.)  A high percentage of the
breakage seems to result native premature deallocation bugs that are likely
to result in nearly undebuggable memory smashes.

Having said that, I have a few concerns that I think we need to address:

1) Finalizers are not the only problem.  Java.lang.ref is also affected.  I
suspect the specification for class unloading also is, though I'm not sure
that reflects a real problem, while the others do.

2) I have zero intuition about the extent to which class hierarchy analysis
is effective at limiting the cases in which we need to extend reference
lifetimes, especially when you consider java.lang.ref.  Even without
java.lang.ref, I would have expected class Object fields to be fairly
common?  On the other hand, without java.lang.ref, we should at least be
able to exclude Strings and the like.  With j.l.r., I think that's hard.  I
can create a WeakReference to a String, and have it enqueued.  I can't
think of a programming idiom for which premature enqueuing of Strings
actually causes a problem, but there are similar idioms (e.g. files with
native descriptors replicated in both the object and the WeakReference)
that seem to be hard to recognize or distinguish from the String case.

3) It's not clear to me that we can ignore expression temporaries when it
comes to lifetime guarantees.  what about
new(finalizable_T).get_some_field().f(), where finalizable_T's finalizer
clobbers some_field, as it's likely to.

The good news, in my opinion, is that given the fact that most existing
code in this areas is already broken and, as Jeremy points out, the
constructs we're talking about are rarely used, we probably have more
leeway than usual to redesign stuff.

Hans




On Sun, Aug 3, 2014 at 4:26 PM, Jeremy Manson <jeremymanson at google.com>
wrote:

> On Sun, Aug 3, 2014 at 3:50 PM, David Holmes <david.holmes at oracle.com>
>  wrote:
>
> >
> >
> > On 4/08/2014 2:39 AM, Jeremy Manson wrote:
> >
> >> I brought finalization up with Aleksey at the summit.  I have another
> >> idea.
> >>
> >>
> >> I assert that not providing stronger guarantees for finalization buys us
> >> approximately nothing.  A quick scan of a large code base suggests that
> >> only 1/10K classes have finalizers.  Optimizing something that rare is
> >> deeply unlikely to buy us any performance in practice.  On the flip
> side,
> >> to provide a meaningful guarantee, all we need to do is add a bunch of
> >> checks in the compiler that prevent making certain optimization choices
> >> when an object is finalizable.
> >>
> >
> > But presumably the key here is to optimize the "is this object
> > finalizable" check?
>
>
> The JIT has to do it.  And it's doing CHA anyway (and if there is a new
> loaded class that invalidates the CHA, the method has to be
> deoptimized/reoptimized anyway).  So I'm not worried about the cost of
> checking.  Mostly, I'm worried that no one would want to do the surgery on
> the JVM, since, IIRC, the original live range is thrown away before these
> sorts of optimizations take place.
>
>
> Furthermore, this is one of the rare cases in the model for which the weird
> >> behavior comes up all the time in practice: I get questions from people
> >> who
> >> run into this behavior several times a year.
> >>
> >> Furthermore, keepAlive() is going to confuse people.  When people
> actually
> >> remember to use it, it will make code less readable, and will likely
> >> simply
> >> be sprinkled throughout the code in the same way that people add
> volatile:
> >> as a mystic incantation that removes weird behavior.  It's going to need
> >> to
> >> be average users - the consumers of a library - who need to use it, and
> >> they will invariably need to run into this problem and debug it before
> >> they
> >> know they have to use it.
> >>
> >> 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.
> >>
> >
> > I think getting rid of the possibility that an object can be gc'd and
> > finalized whilst a thread is in the middle of executing a method on that
> > object would be good thing.
> >
> > I'm not sure what you just expressed about the scope is appropriate
> > though. If you explicitly null a local and invoke GC you should expect to
> > be able to trigger finalization of that instance.
> >
>
> Although providing a reachability guarantee for when the local variable
> doesn't contain a reference to the object at the end of the scope would be
> better, I don't know how to enforce it efficiently.  The only way I can
> imagine doing it is if you know the complete type of everything that might
> be transitively reachable from an object whenever you assign to a local
> variable that currently references that object.  That starts to get beyond
> what we might be able to do efficiently.  I think!  JIT experts, chime in
> if I'm wrong.
>
> (Of course, I guess with my guarantee, where you only get the guarantee if
> the variable still contains a reference to the object when it goes out of
> scope, it would still be confusing, so there's that downside.  At least we
> would still get the case where an object can be gc'd in the middle of
> executing a method on it - the "this" reference would keep it alive.)
>
> I wonder if we could say that, if you reassign / null a variable that
> *directly* points to an object that is finalizable, then *that* provides
> the happens before relationship we want.  That would probably get most of
> the rest of the cases we care about.  It turns into something closer to
> keepAlive in that case, but it wouldn't be quite so mystifying.
>
> Jeremy
>


More information about the jmm-dev mailing list