RE: JavaMemoryModel: A problematical case for finalizers

From: Boehm, Hans (hans_boehm@hp.com)
Date: Wed Apr 09 2003 - 19:07:06 EDT


> -----Original Message-----
> From: Jerry Schwarz [mailto:jerry.schwarz@oracle.com]
> At 11:44 AM 4/9/2003, Boehm, Hans wrote:
> >It looks to me like this discussion is going around in circles.
>
>
> Sorry, but I thought Bill was indicating that it would fix
> the problem in
> with the current specification.
In that case, I agree. But my impression was that Bill had also agreed that we need to fix the spec in this regard, since he tacked this on to his latest proposal as well. Bill?

I think Joshua objected to it along with several other parts of Bill's proposal, but I had assumed he had overlooked it?

There was also an objection stemming from a desire to avoid synchronization in finalizers. But that just can't be avoided, especially in Java. (Bill's example almost certainly synchronizes somewhere inside the library calls, for example.)
>
>
> > I thought we basically agreed that we need to require that
> > synchronized(x) ... ensures that x is reachable at some
> point at which
> > the lock is held. The arguments for this are:
>
> I thought the intention was that it would have to be reachable at the
> "unlock". Requiring it only at "some point" seems inadequate.
Bill pointed out in an earlier message that so long as both the last regularly called method and the finalizer were synchronized, it didn't matter. The object could become unreachable "too early", but the finalizer would have to wait for the lock, anyway.

If you require reachability at the "unlock", the finalizer itself might not have to synchronize (for this reason). But I think that has negligible performance impact. And if you really wanted to make that legal, you'd need to retain Bill's and Jeremy's current visibility rules for finalizers. (I'd really prefer to get rid of those, since it's a lot of text to cover a fairly esoteric case. And the more I think about it, the less I'm convinced you can get away without the lock anyway.) Thus in my view is that it really doesn't matter what the spec says here. (Of course, it needs to be clear one way or the other.)
>
> >1) Without it all existing code involving finalizers is broken.
>
> I believe all existing code is potentially broken because the
> definition of
> reachability is so ambiguous. I'm guessing that nobody
> notices it because
> optimizers aren't doing things that they might do in theory,
> but are too
> hard to do in practice.
We agree. The fact that 90% of the world is currently on X86, where nearly all pointers tend to get spilled to the stack at some point, is probably also a factor. (AFAIK, no 64-bit architectures, not even X86-64, share that property.)
>
>
> >2) AFAIK, it is already satisfied by existing implementations.
>
> I'm willing to accept that all existing implementations put
> the lock into
> the object.
I wrote the code that caused gcj to no longer do that on several platforms. But objects are currently still reachable at an unlock, though that's only true because the unlock isn't currently inlined.

> I would be a bit surprised if all implementations
> never removed
> a lock/unlock pair.
I suspect some do remove locks. (I know that gcj doesn't. Not intentionally, anyway.) I don't think any of them do lock removal if finalization is involved, since that means the object is accessed by at least two threads. (In the synchronized version of Bill's example, the lock removal would be incorrect under the current spec.)
>
> >3) I don't think it has any significant impact on potential future
> >optimizations.
>
> >4) None of the other proposals replace it.
> (System.keepLive() could be
> >used instead, and should perhaps be used in some
> performance-critical
> >code. But even if we adopt that, I think full
> synchronization should also
> >work. It's far easier to explain. And it's cheaper if you need to
> >synchronize anyway.)
>
> Personally I think the System.keepLive is easier to explain,
> but I haven't
> actually tried to explain either mechanism to anyone yet.
>
> If stuff is synchronized because of threading issues anyway
> then I have no
> problem with that also affecting reachability. But telling people to
> synchronize all the methods in a class if it has a finalizer
> seems a bit of
> overkill.
Assuming we drop the current special visibility rules for finalizers, you can explain the synchronization rules for finalizers as follows:
0) Finalizers run in a separate thread. (The JLS already says that, essentially.)
1) You don't want regularly called functions to see the memory effect of finalizers.
2) It would be nice if finalizers saw the effect of regularly called functions.
3) Thus the user needs to enforce ordering by synchronizing regular calls and finalizers.
4) BTW, this also ensures that objects remain reachable as long as you think they should be.
5) If you want to use volatiles to ensure visibility, you have to read the fine print. (And we haven't yet agreed on what that is. I suspect it will basically say that the volatile has to be in the object to ensure the expected reachability behavior. That's still simpler and easier to justify than Bill and Jeremy's current spec.)

Hans
>
> >5) Most people, even on this list, seem to be surprised that
> this isn't
> >already the case.
> >
> >Assuming we deal with finalization issues at all, why would
> we not want to
> >require this? (And I now think we have to deal with these
> issues, since
> >they interact too much with other parts of the proposal.)
> >
> >If we do add System.keepLive(), I would first require that a write a
> >volatile field makes an object reachable (which makes sense
> to me anyway),
> >and then explain the semantics in terms of a hidden volatile
> field, as
> >Bill did.
> >
> >Hans
> >
> > > -----Original Message-----
> > > From: Jerry Schwarz [mailto:jerry.schwarz@oracle.com]
> > > Sent: Wednesday, April 09, 2003 9:25 AM
> > > To: Bill Pugh; javamemorymodel@cs.umd.edu
> > > Subject: Re: JavaMemoryModel: A problematical case for finalizers
> > >
> > >
> > >
> > >
> > > Bill writes
> > >
> > >
> > > >Note that declaring the send and finalize methods as
> > > synchronized with fix
> > > >the problem.
> > >
> > >
> > > I believe it does not.
> > >
> > > A. The lock might not part of the object. It might, for
> > > example, be pointed
> > > to by the object. The code would then only need to hold a
> > > pointer to the
> > > lock, not to the object itself. This would be plausible if
> > > there was some
> > > hardware synchronization operations that only worked on a
> > > specific area of
> > > memory. (I have no idea if there is any such hardware.)
> > >
> > >
> > > B. Consider the code
> > >
> > > Foo f = new Foo(...);
> > > f.send(...);
> > > f = null;
> > >
> > > In this case the compiler can determine that f is never
> > > accessible from
> > > another thread and therefore it doesn't have to actually
> perform the
> > > lock/unlock and so it doesn't need to hold onto the pointer
> > > to f. (It still
> > > needs to do memory barriers as appropriate, but those do not
> > > always require
> > > use of the pointer to f).
> > >
> > >
> > > > So would a call to System.keepAlive(this) at the end of
> > > the send method,
> > > > if people are interested in that.
> > >
> > >
> > > This is the only way. And this will work only if the semantics of
> > > System.keepAlive specifically require that it do so. Merely
> > > giving the
> > > semantics in terms of operations on a hidden volatile doesn't
> > > do the job
> > > because it is possible (indeed we expect) that the compiler
> > > will optimize
> > > away the actual operations on the volatile.
> > >
> > > At 07:43 AM 4/9/2003, Bill Pugh wrote:
> > > >In talking about finalizers with Doug Lea, I mentioned a
> > > scenario which I
> > > >thought everyone knew about but Doug hadn't considered. He
> > > suggested I share it
> > > >with everyone.
> > > >
> > > >Consider
> > > >
> > > >public class Foo {
> > > > OutputStream out;
> > > >
> > > > public Foo(...) {
> > > > out = ...;
> > > > }
> > > >
> > > > public void send(...) {
> > > > byte [] buf = ...;
> > > > out.write(buf);
> > >
> > > > }
> > > > protected void finalize() {
> > > > out.close();
> > > > }
> > > > }
> > > >
> > > >Now consider the code:
> > > >
> > > > { Foo f = new Foo(...);
> > > > f.send(...);
> > > > }
> > > >
> > > >Now within the code for send, a VM might try to optimize
> the call to
> > > >out.write into a tail call to out.write.
> > > >
> > > >The VM could also inline the call to send.
> > > >
> > > >In this situation, there wouldn't be any live reference to
> > > the Foo object
> > > >from the stack during the call to write. However, the write
> > > method could
> > > >take a very long time to execute if it is pushing a large
> > > amount of data
> > > >down a narrow pipe. So the Foo object could be finalized,
> > > and the output
> > > >stream be closed, while the output stream is still being
> written to.
> > > >
> > > >Are we happy to declare such code broken?
> > > >
> > > >Note that declaring the send and finalize methods as
> > > synchronized with fix
> > > >the problem. So would a call to System.keepAlive(this) at
> > > the end of the
> > > >send method, if people are interested in that.
> > > >
> > > > Bill
> > > >-------------------------------
> > > >JavaMemoryModel mailing list -
> >http://www.cs.umd.edu/~pugh/java/memoryModel
> >
> >-------------------------------
> >JavaMemoryModel mailing list -
http://www.cs.umd.edu/~pugh/java/memoryModel
>-------------------------------
>JavaMemoryModel mailing list - http://www.cs.umd.edu/~pugh/java/memoryModel
-------------------------------
JavaMemoryModel mailing list - http://www.cs.umd.edu/~pugh/java/memoryModel



This archive was generated by hypermail 2b29 : Thu Oct 13 2005 - 07:00:44 EDT