Finalize Instead Of Proper Destructor

[Voting on JavaDesignFlaws page.]

Without C++'s destructor we need to wrap every resource encapsulation like so ...

 Resource r = null;

try { r = ResourceManager.allocate(); // do risky, exception throwing stuff } finally { if(r != null) r.deallocate(); }

...or else risk leaking non-memory resources. Memory is an import resource, but not so important that you should manage it at the expense of network connections, database cursors, and graphics contexts.


What about:

 Resource r = ResourceManager.allocate();
 try {
    // do risky, exception throwing stuff
 } finally {
    r.deallocate();
 }

The try-block ensures deallocation when the resource has been successfully allocated. It does not protect the allocation itself.

Of course you are technically right here, and that is what I wrote at first, but when you have the more general case of allocating several resources at once you want to implement a transaction-like mechanism. You can either do this by nesting several try-finally resource allocations or by expanding what's at the top of the page.

["finally" is a bad replacement for destructors as you have to think of it at every use, while destructors are implemented OnceAndOnlyOnce. A better replacement is "with" in C#, but to make it safe, "with" should also wrap the allocation and a with-like method should be the only way to allocate some ressources. This only works if you have lightweight syntax for anonymous functions (lambda or blocks), which was "forgotten" in Java.]


This seems largely a syntax issue - caused by those awful 'try/catch/finally' blocks. The 'Finalize' wouldn't be nearly so bad if it had better syntax for its application (such that it could be utilized with macros and provided tighter locality of reference). Consider a slightly different syntax:
 {
   r1 = ResourceManager.allocateX();
   fini { r1.deallocate(); catch(e) { send_to_log(e); }}
   r2 = ResourceManager.allocateY();
   fini { r2.deallocate(); }
   // do risky, exception-throwing stuff that throws both handled and unhandled exceptions or perhaps returns prematurely...
   catch(handled_exception& e) { ... }
 } // r2.deallocate() and r1.deallocate() called here, in that order, with any exceptions squelched (but logging exceptions from r1.deallocate())

This is a bit verbose on its own, but it makes for very easy use with expansive macros (which can expand into both the allocation and the associated 'fini' block). Importantly, it would be meaningful even in those languages that are either not strongly object-oriented (functional or procedural languages) or that exclusively use garbage collection (w/o static analysis to determine what it can kill ASAP) and, thus, either don't provide 'destructors' or do so but execute them with highly erratic timing.

A side-note: DeeLanguage has this exact feature, written as scope(exit) obj.dealloc(). That and scoped(Foo).


Finalize does have a nice property in that it would provides a clean delimiter for the legal extent of continuations (ContinuationPassingStyle) - basically the idea being that any 'finally' block must be executed at least once AND at most once. Unfortunately, there are no languages that use ContinuationPassingStyle that also perform this sort of analysis for procedural correctness.


Unfortunately, the semantics of the C++ destructor have problems -- the transitive closure of the dead object is traversed, precluding the reliable use of generation-scavenging garbage collectors. The constructor and destructor semantics of C++ are among its most apparent failings, and motivation for Java to leave them behind.

[C++ destructors have perfectly well defined semantics. They just don't map to Java. It is well known that Java's designers didn't like C++, but that doesn't make it nearly as broken as you imply here.]

I don't understand this. Partly because it has a high GingerFactor, and partly because I don't think it makes any sense when my mind untwists it:

Actually, I believe the anonymous comments are similar to TomStambaugh's comments on the other GarbageCollection pages. So, if I interpret the sentence thusly, I respond: No, the destructor semantics are still well-defined. Yes, you have to search for dead objects as well as live objects, which is the same in other good garbage collected languages (not Smalltalk--stupid thing leaks file handles).

Now, a good reason why Java can't add destructors now is that Java explicitly allows an object to ressurrect itself in the finalize() method by building a strong reference to it somewhere. e.g. finalize() { GodObject.instance.register(this); }, whereas once a C++ destructor has been called, the object is non-resurrectable. Consider this scenario:

 struct God { ... };
 struct Base {
     // Resurrect myself
     virtual ~Base() { God.pInstance->push(this); } 
 };

#include <cstdio> using namespace std; struct Subclass : Base { Subclass() { pFile = fopen("foo","r"); } ~Subclass() { fclose(pFile); } FILE *pFile; }; ... { Subclass *pSubclass = new Subclass; } // Force loss of *pSubclass...

...

// ... which God resurrects in the meantime (lame, I know, but whatever)

...

Subclass *pSubclass = dynamic_cast<Subclass *>(God.pop()); fgetchar(pRef->pFile); // CRASH! -- pFile is invalidated

Of course, this will break in Java too if Subclass deallocates a resource in its finalize() and then Base resurrects the object when it gets a turn. But that's why resurrection is evil. If you're looking for design flaws, that would be a good one. -- SunirShah


Java only collects resources it knows about. You can tie your resource management to the garbage collector via finalize() but this isn't called immediately. Many Java garbage collectors don't run if the machine is busy, leading to escalated memory usage. The collectors in VisualAge VM and the JDK thrash. Also referenced but forgotten resources might need deallocation of elsewhere in the code.


What about the following block in Resource:

  public void finalize() {
    this.deallocate();
  }

If it's important enough to explicitly free up a resource when it is no longer needed, then it should be explicit. If it's not that important then it's OK to leave it up to the garbage collector to reap the object when memory is needed. For the in between case, a pool of instances would be an ideal solution, so you'd do something like this:

  try {
    // blah blah 
   } finally {
     ResourceManager.return(r);
   }

And write Resource to be able to handle being re-used. -- StevenNewton

Say that again?

If you have resources allocated that you absolutely must be sure get released at a certain time (for example to avoid deadlock or something like that) then you should explicitly release those resources. If you don't really care when the resources get released, just wait for the garbage collector to clean them up. The fact that you need to free up resources is undeniable. When that is done depends on the context.

I still don't follow. One doesn't want to explicitly put clean-up code at every return value, for instance, because that sort of thing quickly gets out of sync. It's much better to have access to the language's built-in scoping mechanism directly, as with C++ destructors, or with Java and C#'s finally. C++ destructors have an advantage of being automatic and centralized, so you don't have to depend on the client programmer to remember to clean up. However, destructors don't make sense in a garbage collected environment, so Java added finally. Trading explicit memory management for explicit resource management. -- SunirShah

If you don't really care when the resources get released, just wait for the garbage collector to clean them up.

This is not entirely true. You want your objects to correspond to real world items. When you release your object you also want the real world item released. You do not want to discover when you create an object that its real world counterpart is still locked waiting for the garbage collector. Make the object and its resource identical simplifies management of the resource especially under error conditions.

[It also doesn't work. Released resources may not ever get cleaned up, at it is not guaranteed that finalize() will ever be called.]

By the way, what is shown above is not the same as the finalize() method which does exist in Java and serves the same (or at least a very similar minus the memory de-allocation stuff) function as the C++ destructors. What is shown above is the third part of a try-catch block which is a feature that does not (to my knowledge) exist in C++. That third part is guaranteed to execute regardless of the result of the try-catch part. It has nothing to do with the destruction of the object (the one that contains the try-catch block).

I wouldn't agree that finalize() is like a destructor. They don't have similar behavior nor does finalize() have the same guarantees as a destructor. But let's be honest, destructors in Java would make very little sense

Well, what might make sense is some mechanism to say "as soon as this object is no longer reachable, call this function so I can free resources it no longer needs". Currently, the Java guarantee that finalize is called "before the memory the object occupied is reused" seems a little late in the day. -- AnthonyLauder

But this is exactly what finalize() does! As soon as an object is known to be not reachable, finalize() is called. That is, at GC time. O, you want the runtime to note immediately when you drop the last reference? Sorry, that is not possible without basically doing a full GC after every assignment (or giving up on cycles and use refcounting). So the Java runtime is really doing the best you could hope for. Now, we could say to the language: "I promise I will only use this resource in the dynamic context of the current method", which is basically what C++ forces you to do, or manage lifetime by hand. If you make this promise, then the language can clean up the object after you leave the current dynamic context. That's what destructors do. That's, incidentally, also exactly what finally does. Now I agree that the nice thing of destructors is that they are called automatically. However, the right way to solve this in a GC language is not to add destructors, but to use a pattern I called ResourceAcquisitionIsInvocation. Which is used a lot by Smalltalk, Lisp, Scheme and other winning languages.

-- StephanHouben

I don't understand what you mean when you say

"I promise I will only use this resource in the dynamic context of the current method", which is basically what C++ forces you to do.

There's nothing in C++ that forces method scoping. Much later... I'm still particularly perturbed about this claim. C++ gives you much more freedom in choosing how you manage resources than Java or Smalltalk. You are free to use lexical scopes or dynamic scopes or self-management or even garbage collection. So, if you are going to make a claim like that, you need to be much more accurate in your description.

On the other hand, note that these other languages have opted for garbage collection simply because the types of resources they manage are typically constrained by the provided language libraries. Thus, the language implementors can "see" all the resources in question. As soon as you take these languages out of their chosen problem domains to touch new contexts, you are left with the cruddy and unmaintainable task of manual balancing. But, you see, you're not supposed to do that with most garbage collected languages, so it's really handy for the collector to be there. Consider that Java collects all sorts of useful objects from memory, to files, to sockets, but not database connections. -- SunirShah

SmugLispWeenies will contest that "unmaintainable" claim. LispMacros make it easy. -- DanBarlow

Actually, functional programming style make this easy, and you can do this in most languages:

    Block>>value: anObject handle: onException final: aFinalizationBlock
        self value: anObject handle: onException.
        aFinalizationBlock.!!

Object>>with: aBlock handle: onException | anInstance | anInstance := self new. aBlock value: anInstance handle: onException. anInstance finalize.!!

and instance methods

     Object>>finalize
        !!

MyResource>>finalize self cleanup!!

and then write

    MyResource with: [ some stuff ] handle: [ on exception ].

whereas in Java, one would need

    interface Destructable {
         void destruct();
    }

class Scope { abstract void eval( Destructable object );

static void with( Destructable object, Evaluatable evaluatable ) throws( Throwable ) { try { evaluatable.eval( object ); } finally { object.destruct(); } } }

class MyResource : implements Destructable { ... }

... // Code fragment Scope.with( new MyResource(), new Scope { void eval( Destructable anObject ) { MyResource resource = (MyResource)anObject; do stuff with anObject; } });

and Perl--well, it's not necessary with Perl because it is reference counted, but they plan to add a garbage collector soon, so--

    package Destructable;
    sub with($&) {
        my $class = unshift;
        my $self = new $class;
        my $code = unshift;
        eval { &$code };
        destruct $self;
    }
    1;

package MyResource; @ISA qw( Destructable ); sub destruct { my $self = shift; $self->cleanup; }

... # Code fragment

with MyResource { $self->doStuff(); };

but this only helps you manage one resource at a time, and it still doesn't help you if you create a MyResource directly. -- SunirShah


In ForthLanguage, you can define a LATER> construct, like this:

 : LATER>  R> R> SWAP >R >R ;

which can be used to manage resources too.

 : rtuck
   POSTPONE R> POSTPONE SWAP POSTPONE >R POSTPONE >R ; IMMEDIATE

: WITH-FILE OPEN-FILE THROW DUP rtuck LATER> R> CLOSE-FILE THROW ;

: WITH-MEMORY ALLOCATE THROW DUP rtuck LATER> R> FREE ;

etc. Typical use:

 : dump-logs
   S" myLogFile" R/O WITH-FILE BEGIN dup at-eof? NOT WHILE dup get-and-print-line REPEAT ;

--SamuelFalvo?

"I promise I will only use this resource in the dynamic context of the current method", which is basically what C++ forces you to do, or manage lifetime by hand.

Have you heard of SmartPointers? You can make the object take care of itself. You can also use a garbage collector to have the system reap the object for you. --ss

Well, the beginning of this page already contains a discussion why GC should not be used to release other resources than memory. In particular, conservative GC (the only option available for C++) doesn't give any guarantees that a finalizer will be called at all. So GC+finalizers is only a viable option if you actually don't care for the resource to be cleaned up, in which case just leaking the resource works fine, too ;-)

As for reference counted "smart" pointers: this is effectively a virtualisation strategy, and only moves the problem from managing the original resource to managing the pointers, which again gives you the 2 choices we had before: rely on a dynamic context to clean them up or manage them explicitely. I recognise that smart pointers can make things more tractable, but they don't fix the fundamental problem of resource management. Note that this is not a critique on C++: after all, determining the exact spot in a program after which a resource is no longer needed is uncomputable. -- StephanHouben

Well, what I'm tripping on is basically what's the big difference between SmartPointers and garbage collection? (Noting that I've written a few SmartPointer-based mark-and-sweepish GCs before.) --ss

SmartPointers can implement GC. They can also implement different things. In particular, SPs can implement reference counting and thereby support complex usage patterns (though without cyclic references) with prompt destruction.


What Java really needs is a standard interface for deallocation so we don't have to look up what method we need to call each time. Perhaps java.lang.Resource could declare a standard destroy() method (or java.lang.Object itself!), and we wouldn't have to remember close(), delete(), kill(), and whatever else names are used for deallocation by different classes.

Perhaps it should take a leaf out of C#'s book, with the Dispose() method defined in the interface IDisposable (should be implemented by any class that has resources to manage.)

Of course, MicroSoft then proceeded to gild the lily with the using operator:

 ...
 using (SomeDisposableThing theThing = new SomeDisposableThing ())
 {
   [do some work here]
 }

and now theThing.Dispose() will be automatically called when it goes out of scope.

Hey, for all practical purposes in C# both lock and using are macros. lock(obj) stmt expands to

  Monitor.Enter(obj);
  try {
    stmt
  } finally {
    Monitor.Exit(obj);
  }

and using(var-declaration-with-initialization) stmt expands to

  {
    var-declaration-with-initialization
    try {
      stmt
    } finally {
      ((IDisposable)var).Dispose();
    }
  }

Honestly, they should borrow a page from NemerleLanguage and build in extensibility rather than individually sweetening officially sanctioned patterns. -- JeffreyHantin


I've found that most external resource problems go away when you TellDontAsk an object what to do. Don't give clients the opportunity to leave resources hanging - provide methods which handle it for them. Hmm, maybe I'm trying to say MakeUnmanagedResourcesPrivate?. Oh, having just read ResourceAcquisitionIsInvocation, maybe that's a better pattern, although I can't be sure because I didn't grok the pseudo-code :-)


Do the PhantomReference and ReferenceQueue classes help? I doubt it. They just tell you when a referent is finalized, right? -- EricJablow


"I promise I will only use this resource in the dynamic context of the current method", which is basically what C++ forces you to do, or manage lifetime by hand.
Really? LinearTypes (possibly implemented via C++'s unique_ptr) ensure that there is no unmanaged sharing of resources--everything has exactly one owner (T foo) and other objects may borrow it (T& foo) or ownership may be transferred to them (T&& foo). Making sure to null old references after transferring ownership seems like enough copy-paste code to cause bugs (rather than simply annoying the programmer for his/her constant typing of try { } finally { }).


CategoryJava

EditText of this page (last edited September 8, 2013) or FindPage with title or text search