Domanda

Java

In Java, there is an idiom called "Finalizer Guardian" which prevents subclasses overriding superclasses finalizer but forgetting to invoke it. Here is an example from Effective Java Item7:

// Finalizer Guardian idiom
public class Foo {
  // Sole purpose of this object is to finalize outer Foo object
  private final Object finalizerGuardian = new Object() {
    @Override protected void finalize() throws Throwable {
      ... // Finalize outer Foo object
    }
  };
  ... // Remainder omitted
}

With this technique, even if a subclass having a finalizer doesn't invoke superclass's finalizer, the private object would run the finalization code of the superclass.


C#

However, in C# in a Nutshell, section "Calling Dispose from a Finalizer", there is an example like this:

class Test : IDisposable {
  public void Dispose() // NOT virtual {
    Dispose (true);
    GC.SuppressFinalize (this); // Prevent finalizer from running.
  }

  protected virtual void Dispose (bool disposing) {
    if (disposing) {
      // Call Dispose() on other objects owned by this instance.
      // You can reference other finalizable objects here.
      // ...
    }
    // Release unmanaged resources owned by (just) this object.
    // ...
  }

  ˜Test() {
    Dispose (false);
  }
} 

The author also stated that:

The disposing flag means it’s being called “properly” from the Dispose method rather than in “last-resort mode” from the finalizer. The idea is that when called with disposing set to false, this method should not, in general, reference other objects with finalizers (because such objects may themselves have been finalized and so be in an unpredictable state)


Question

But, when we review the Finalizer Guardian idiom of Java, the inner private guardian object actually referring/finalizing the outer object which could have a finalizer itself. It violates what the author of C# in a Nutshell stated.

I am curious about why "referring other finalizable objects in a finalizer" is possible in Java but not in C#. Thanks for answering.

È stato utile?

Soluzione

First of all in C# one can't "forget" to call base class finalizer in derived class because there is no syntax to override base class finalizer - it will be called always (similar to constructors).

Indeed one can override Dispose and forget to call base class' version - in this case implementation of finalization in base class will be skipped. On other hand when properly implemented finalize-able classes in C# would not cause fatal issues if derived class forgets to dispose parent one - classes that really manage native resources (like OS handles) should be sealed and thus protected from the issue (as one can't override any methods in this case).

On second half about finalizing order:

I don't know how or if Java finalization guarantees that objects are finalized in consistent order so all references are valid till all finalizers finished...

In .Net/C# finalizers order is undefined - meaning after determining what objects need to be finalized (due to lack of external references) objects from that set will get finilizer called without any particular ordering. As result if objects in the set refer to each other than when finalizer of last object is called all other ones are already finalized.

Altri suggerimenti

I think it's not that simple. I think you can reference other objects in .net and java alike and the same problem exists about the other objects being in an unpredictable state. One would need to study the inner workings of both to fully understand them, but I don't know java that deeply, I only think that they are very similar. And by the way, this finalizer guardian looks fishy to me.

You don't show the code for the finalizer guardian, but I would suggest that one should avoid having a finalizable object hold a reference back to the original. Instead, those aspects of the original which would need cleanup should be encapsulated within the finalizable object (possibly using an AtomicReference or AtomicInteger). For example, an object which encapsulates an OS file handle could keep the handle itself encapsulated within a private finalizable object that holds the handle in an AtomicInteger. If the outer object gets abandoned, the handle can be cleaned up without anything having to access the outer object. If the outer object is asked to close the file, it can relay the request to the inner object, which can then read and clear the AtomicInteger where the handle is stored (thus ensuring the file can get closed only once).

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top