Created: 10/9/96 PatrickB

Return to the Java Hall of Shame

Using Finalizers to Violate Monitors

Because Finalizers can run in any thread, even threads that hold monitors, finalizers can be used to violate the intuitive model of monitors in Java. The following program illustrates the problem by causing it to occur in Sun's VM:
/* 
 * This program demonstrates a legal but very bogus interaction 
 * between finalizers and java's recursive monitors that
 * breaks synchronization, even across module boundaries.
 */

// A completely innocuous class that uses synchronization, but makes the 
// (understandable) mistake of creating new objects inside a monitor 
class helper_class {
    // Sanity check synchronization by keeping track of the number
    // of times we are in baz() concurrently
    private int recursion_level = 0;
 
    private void check_recursion(int expected) {
	if (recursion_level != expected) {
	    System.out.println(" * WHOA - baz() is a synchronized routine." +
			       " I never expect to call myself, so\n" +
			       " * recursion_level should be " + expected +
	    		       " but it's actually " + recursion_level + 
			       "! - EXITING");
	    Thread.currentThread().dumpStack();
	    System.exit(-1);
	}
    }

    // This routine is going to do some nasty timing sensitive stuff. 
    // Make it synchronized so that we don't have to worry about it.
    // Since we don't call ourselves recursively, there should be 
    // at most one copy of this routine going at a time. RIGHT?
    public synchronized void baz() {

	recursion_level++;
	check_recursion(1);

	/* Alloc lots to force a gc and hopefully to cause finalizers 
	 * to run (explicit invocation via Runtime.runFinalizers() also 
	 * works)
	 */
  	Integer a[][] = new Integer[1000000][];
	for (int i = 0; i < 1000000; i++)
	    a[i] = new Integer[1000000];

	recursion_level--;
	check_recursion(0);
    }
};

class finalizable_class {
    static helper_class global = null;

    public void finalize() {
	System.out.println("In finalizer from thread " + 
			   Thread.currentThread().getName());
	global.baz();
	System.out.println("Leaving finalizer from thread " + 
			   Thread.currentThread().getName());
    }
}

class TestThread extends Thread {
	public void run() {
	    /* Make some garbage in a completely separate thread */
	    new finalizable_class();
	}
};

class tester_class {
    public static void main (String args[]) throws InterruptedException {
	helper_class h = new helper_class();
	finalizable_class.global = h;

	/* Create garbage in a separate thread, and then join with 
	 * that thread */
	TestThread t = new TestThread();
	t.start(); 
	t.join();

	System.out.println("Calling baz from the main thread.");
	h.baz();
	System.out.println("Exiting from the main thread.");
    }
}

And the output from running this program is:
> javac exa.java
> java tester_class
Calling baz from the main thread.
In finalizer from thread main
 * WHOA - baz() is a synchronized routine. I never expect to call myself, so
 * recursion_level should be 1 but it's actually 2! - EXITING
java.lang.Exception: Stack trace
	at java.lang.Thread.dumpStack(Thread.java)
	at helper_class.check_recursion(exa.java:21)
	at helper_class.baz(exa.java:33)
	at finalizable_class.finalize(exa.java:54)
	at helper_class.baz(exa.java:33)
	at tester_class.main(exa.java:79)
Since finalizers can be (and are) run from any thread, and thread locks are recursive, if a thread runs a finalizer while it already holds a monitor lock, we potentially break synchronization in a completely unsuspecting class. If thread locks weren't recursive, this would cause deadlock instead.

The real culprit is the potential running of finalizers by any thread. Allowing threads that potentially hold locks to run finalizers that may also need to synchronize on monitor locks is a very bad thing, potentially violating synchronization assumptions or causing deadlock. Return to the Java Hall of Shame