Friday, April 01, 2005

Resurrecting Java objects

The semantics of finalization in Java dictates that a finalizer can be run at most once. So if an object revives itself from within its finalizer, then the next time the garbage collector tries to collect it, the finalizer isn't run. Here's a class that refuses to die:
import java.util.Hashtable;

public class Immortal {

/** tracks the number of times each finalizer is run */
private static Hashtable<Integer,Integer> finalizeCounts =
new Hashtable<Integer,Integer>();

/** used to control object lifetimes */
private static Hashtable<Integer,Immortal> pointers =
new Hashtable<Integer,Immortal>();

/** used to generate unique identifier codes */
private static int unique = 0;

/** deletes the object with the given id. */
public static void kill(int id)
{
int finalizeCount = finalizeCounts.get(id);
pointers.remove(id);
while (finalizeCounts.get(id) == finalizeCount)
{
System.out.println("trying to kill " + id + "...");
System.gc();
}
}

// The code from these two methods can't be inlined, because
// we rely on their stack frame disappearing to prevent the
// link to the tracked object from persisting.

public static int makeTemporaryObject()
{
Immortal temp = new Immortal();
return temp.id;
}

public static void doSomethingWith(int id)
{
Immortal temp = pointers.get(id);
temp.sayHello();
}

/** identifier code */
private int id;

private Immortal()
{
id = unique++;
System.out.println("creating instance " + id);
finalizeCounts.put(id, 0);
pointers.put(id, this);
}

public void sayHello()
{
System.out.println("hi, I'm number " + id);
}

public void finalize()
{
System.out.println("finalizing " + id + "...");
finalizeCounts.put(id, finalizeCounts.get(id) + 1);
// clear! *khzh-thump*
pointers.put(id, this);
}

}
And here's an example interaction with the class:
int id = Immortal.makeTemporaryObject();

// This causes the finalizer to run (in the GC thread.)
Immortal.kill(id);

// And yet, the object is still alive!
Immortal.doSomethingWith(id);

// This will now loop infinitely, since the finalizer
// will never be run a second time.
Immortal.kill(id);
The interaction loops infinitely, with the beginning of the output looking something like:
creating instance 0
trying to kill 0...
trying to kill 0...
finalizing 0...
trying to kill 0...
hi, I'm number 0
trying to kill 0...
trying to kill 0...
trying to kill 0...
trying to kill 0...
trying to kill 0...
trying to kill 0...
Notice that the output is interleaved since the GC (and hence the finalizer) runs in a separate thread from the main code.

Update: How embarrassing, I misspelled a world in the title of this post. I hope fixing it doesn't cause problems with the archived file name. Let's see if Blogger can handle it...

3 comments:

Felix said...

Wait, is the instance of the Immortal object really never collected?

Or is your beef really that the finalize() method isn't called the second time that the objcect is actually collected?

That is, can you actually observe a space leak using this class? or are you merely observing the fact that an instance's finalizer is called once and only once?

Felix said...

Just to make some of the issues here more concrete, I put a followup on a blog I hacked up after I saw Dave's blog.

Anonymous said...

You're right, the object is collected and not "immortal". This post is entirely misleading. The loop that's somehow supposed to show the object can't be killed doesn't depend on the object being still alive in any way.

And, after all, what would be the point for the GC to keep an object "alive" that's not accessible at all, not even during finalization? None of its methods can be called again, and none of its fields accessed. Not collecting it would just be a waste of memory (i.e. leak memory).

In order to see whether the object is really still around, one would have to use a WeakReference. And I strongly suspect that it will be collected in the second GC run right away.