[concurrency-interest] [Javamemorymodel-discussion] Fences.keepAlive

Boehm, Hans hans.boehm at hp.com
Wed Jan 14 17:12:45 EST 2009


> -----Original Message-----
> From: Gregg Wonderly [mailto:gregg at cytetech.com] 
> ...
> But PhantomReference makes finalize() unnecessary when you 
> use the Holder pattern that my ReferenceTracker is based on.  
> With that pattern, you don't have anything going on "bad" 
> because the Tracker has a strong reference to the 
> "to-be-released" resource and when the Holder goes out, the 
> Tracker knows which object to use for "releasing" the 
> associated resource(s).  Every object that has associated 
> resources can be dealt with in this way, and any ordering 
> requirements can all be managed via strong references where 
> you are in complete control of the order of operations.
> My Tracker is much like the S object in your paper, so I 
> think you familiar with where I'm coming from.
>  From my perspective, one can stop using finalize and 
> worrying about all these data races by just adopting the 
> pattern that I'm presenting through the use of my 
> ReferenceTracker class.
I think we're talking about completely different things.  The problem here is unrelated to ordering of different finalizer invocations, which can indeed be addressed with java.lang.ref, or with some finalizer usage patterns.

Consider a slightly expanded example.  I have a finalizable class S, which stores some of its data in a global array A.  Each element s of S contains a field index, such that A[s.index] contains the data associated with s.  The finaliser for s cleans up A[s.index].  Or s is referenced by some java.lang.ref and whoever pops it from the reference queue cleans it up.  It doesn't matter.  There are no other finalizable or java.lang.ref objects in the world.  (In some sense, this is the canonical use of these mechanisms, to clean up some external resource.  In simple cases, the external resource is actually an OS resource, but even that doesn't matter here.)

Assume s has a method foo that accesses the internal state as in

T foo()
  int i = index;

  return a[i].bar;

Assume for the sake of specificity that S's finalizer just sets a[index] to null.  (In reality, it would probably also mark the entry as available, which would require some synchronization, as finalizers usually do.)

Assume this is called from a context:

s = new S;
y = s.foo();
// no further accesses to s.

Assume that x in the caller is allocated in a register that is also used to pass the this parameter to methods.  (A good register allocator should do exactly that in this case.)  Assume that the i in foo is allocated to the same register.  (This is fine, because the "this" argument is dead after "index" is looked up.)

Now assume that a simple stop the world garbage collection occurs at point a, and this thread is delayed until all cleanup actions are complete.  Thus A[i] will be set to null at point a, and the return statement will encounter a null pointer exception.  This is probably undesirable, and hence the code is wrong.

Currently this is fixable by putting a synchronized(this){} in the finalizer, and rewriting foo as

T foo()
  int i = index;

  T res = a[i].bar;
  synchronized(this) {}
  return res;

We're proposing to allow this to be written as

T foo()
  int i = index;

  T res = a[i].bar;
  return res;

This is a lot faster, maybe a little less magical, but still not beautiful.  I don't think there's any way to avoid the addition of a user call unless we change the language to guarantee later finalization/reference enqueueing.  And if you think about this hard enough, those guarantees would only be sufficient for what you really want in about 98% of cases (warning: made up statistic), since the finalizer might also do something bad to a[i].bar.  But they would probably "unbreak" a lot of existing code.


> > There are other possible design tradeoffs, but they're more 
> invasive.  
> > Outlawing dead variable elimination for object references 
> works, and 
> > was discussed during JSR133 discussions.  At the time it we 
> felt that 
> > this was risking too much performance impact for a rarely used 
> > language feature.  Although I advocated this position (in 
> part to help 
> > get
>  > JSR133 out the door), I think that from a purely technical 
> perspective  > it's worth considering.
> I personally don't want to see this level of GC 
> implementation detail visible to the developer and a 
> necessary part of their programming knowledge.  finalize() 
> has way too many bad things in it related to ordering etc.  
> Your paper talks about these things in great detail.  
> Exposing them will interfere with the JMM and the GC details 
> if they are made more controllable.
> Rather than say we need a fence at the end of finalize(), why 
> not, instead say, use class XYZ to track references to 
> resources you need to release at the end of an objects life.  
> Then we have a more flexible way to manage this issue it 
> seems to me, and there is not the low level detail of locking 
> that the developer has to deal with.
> > Although C++0x is expected to contain only very minimal GC support,
>  > and no finalization support, this issue was discussed in 
> some detail  > in that context, and I think the options could 
> probably all be  > adapted to Java.  This is largely written 
> up at  > 
> http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2261.html .
> > 
> >> It is also important to note that this topic is very 
> related to the 
> >> memory model. The last point of reachability is just as 
> much subject 
> >> to reordering/inlining optimizations and an object can end 
> up being 
> >> collected even earlier than its last lexical reference.
>  >
> > Right.  And JSR133 at least made the symchronized(this) solution 
> > possible.  But we always knew that this wasn't a great solution.
> It still seems to me that PhantomReference is the better 
> solution than finalize(), maybe you can tell me why that 
> isn't the case.
> > I think keepAlive is really the minimal solution that people might 
> > realistically pay attention to.
> It seems that the .NET solution is to just provide a standard 
> method name which does nothing, so that the compiler is 
> compelled to maintain order of execution and to maintain the 
> reference till the end of the method.  Is there something 
> else happening here?
>  > I have to admit I'm not
> > encouraged by the .NET documentation that Matthias pointed 
> to, since 
> > it also seems to miss the core problem and focus on native
>  > code issues instead.
> Hans, are you only talking about how finalize() works, or is 
> there another path in 100% java code that I still haven't 
> gleaned from this?
> Gregg Wonderly

More information about the Concurrency-interest mailing list