JavaMemoryModel: finalization review

From: Boehm, Hans (hans_boehm@hp.com)
Date: Thu Nov 20 2003 - 19:22:06 EST


I spent a little bit of time revising an example of finalization
use in Java I had constructed earlier. I think is fairly representative
of a reasonable class of finalization uses.

The idea is that I have "resource"s which need to be explicitly "destroy()"ed.
(You can think of them as temp files that need to be removed, though Java
happens to have a built-in API for that.) All such resources are accessed
through the wrapper class "clean_resource" which ensures that
"destroy" is called either in response to the object getting dropped, or in
response to an explicit detroyAll() call just before program termination.

I think the implementation is somewhat instructive, and points out a remaining issue.

Here's what the wrapper class looks like, with my best attempt at correctness,
but no clever performance tricks:

public class clean_resource
{
  static final int N = 1024;
  // The following two arrays are protected by the lock on impls.
    static resource impls[] = new resource[N]; //better data structure??
    static boolean taken[] = new boolean[N];
    
  int my_index;

  public clean_resource(long x) {
    resource my_resource = new resource(x);
    synchronized(impls) {
      my_index = first_available();
      impls[my_index] = my_resource;
      taken[my_index] = true;
    }
  }

  protected synchronized void finalize() {
    synchronized(impls) {
      if (taken[my_index]) {
        impls[my_index].destroy();
        taken[my_index] = false;
      }
    }
  }

  public static void destroyAll() {
    synchronized(impls) {
      for (int i = 0; i < N; ++i) {
        if (taken[i]) {
          impls[i].destroy();
          taken[i] = false;
        }
      }
    }
  }

  public synchronized long doSomething() {
    // synchronized to ensure reachability of this.
    resource my_impl;
    synchronized(impls) {
      if (!taken[my_index]) throw new Error();
      my_impl = impls[my_index];
    }
    return my_impl.doSomething();
  }

  // Caller holds impls lock.
  static int first_available() {
   for (int i = 0; i < N; ++i) {
     if (!taken[i]) return i;
   }
   throw new Error();
  }

}

The remaining problem (if you want to call it that) is that there are two distinct
reasons for synchronization, with two separate locks, and the generic access method
doSomething ends up acquiring them both:

- The lock on impls guards access to the data structure shared by all instances of
clean_resource. (Since the object itself is presumably going away, finalizers tend to
access shared data, and this kind of locking is probably typical.)

- The lock on individual instances needs to be acquired to order finalization after
regular accesses. In particular, otherwise the finalizer could run during the
call to my_impl.doSomething(). In some sense that's unavoidable because the lock
on impls isn't held long enough in doSomething().

What's a little annoying to me is that if we change the code so that every method
only acquires the impl lock, but acquires it around the entire method body (including
the constructor), and hence everything is serialized, the code is still not correct.
doSomething would look like:

 public long doSomething() {
    synchronized(impls) {
      if (!taken[my_index]) throw new Error();
      return impls[my_index].doSomething();
    }
  }

There would be nothing in doSomething that ensures reachabilitty of "this" according to the
spec. Hence we could get premature finalization again. That's a little counterintuitive
(even by my standards), since we are reading a field after acquiring a lock, which normally
implies an actual access to the object. On the other hand, that argument could fail
for final fields, and requiring this only for non-final fields isn't very intuitive either.

Any comments? Is the nested locking OK?

(Note that objects themselves are only written in the constructor, hence the additional
reachability guarantees in the spec don't matter. I expect that's also typical.
One might be able to make this work without the inner synchronization in doSomething()
by making my_index final, but that's overly clever and probably doesn't generalize.)

(The lock on "this" could be replaced by a write to a volatile field in the object, which
is read by the finalizer. That's cheaper, but probably harder to explain.)

Hans

-------------------------------
JavaMemoryModel mailing list - http://www.cs.umd.edu/~pugh/java/memoryModel



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