JDK-4684790 : URLClassLoader will not GC if loaded class registers shutdown hook
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang
  • Affected Version: 1.4.0
  • Priority: P4
  • Status: Closed
  • Resolution: Duplicate
  • OS: windows_nt
  • CPU: x86
  • Submitted: 2002-05-14
  • Updated: 2002-05-31
  • Resolved: 2002-05-31
Related Reports
Duplicate :  
Description
FULL OPERATING SYSTEM VERSION :

Microsoft Windows 2000 [Version 5.00.2195]

ADDITIONAL OPERATING SYSTEMS :

all platforms we tested exhibit this problem, including
Windows 98, Windows NT/2000, Solaris 7, Redhat Linux

A DESCRIPTION OF THE PROBLEM :
If a shutdown hook is registered by calling
Runtime.addShutdownHook(), and the Thread registered is an
instance of a class loaded by a URLClassLoader, the
URLClassLoader will never be garbage collected, even if the
shutdown hook is de-registered by calling
Runtime.removeShutdownHook() and all other references to
objects of classes loaded by this URLClassLoader are
released.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Compile the test programs listed below
(URLClassLoaderBug.java and Stupid.java)
2. Jar up URLClassLoaderBug.class into URLClassLoaderBug.jar
3. Jar up Stupid.class into stupid.jar
4. Run:
java -classpath URLClassLoaderBug.jar URLClassLoaderBug
stupid.jar Stupid


EXPECTED VERSUS ACTUAL BEHAVIOR :
The test program "Starter" takes two arguments: a jar file
name and a class name. It builds a URL for the based on the
Jar file name, instantiates a URLClassLoader, loads the
requested class from the URLClassLoader, creates a new
thread to launch the process, sets the thread context class
loader, and runs the thread. I subclassed URLClassLoader
and implemented finalize() so that I know when it gets
garbage collected.

The test program "Stupid" just prints out some text, and
registers and deregisters a shutdown hook.

When I use "Starter" to run "Stupid", I expect that the
URLClassLoader used to isolate Stupid will be garbage
collected after "Stupid" exits. However, this never occurs.

Note: I had some problems getting garbage collection to run
immediately. I actually ran this program in JProbe and that
was able to force garbage collection.

If you comment out the code in Stupid.java that registers
the shutdown hook, the URLClassLoader will get garbage
collected.

This bug can be reproduced always.

---------- BEGIN SOURCE ----------
Starter.java:
--------------------------------------
import java.net.URL;
import java.io.File;
import java.net.URLClassLoader;
import java.lang.reflect.Method;

public class Starter
{

    public class MyURLClassLoader extends URLClassLoader
    {
	public MyURLClassLoader(URL[] classpath)
	{
	    super(classpath, ClassLoader.getSystemClassLoader());
	}

	protected void finalize()
	{
	    System.out.println("MyURLClassLoader.finalize()");
	}
    }

    public class MainThread extends Thread
    {
	private Method mainMethod;
	public MainThread(Method mainMethod)
	{
	    this.mainMethod = mainMethod;
	}

	public void run()
	{
	    try
		{
		    mainMethod.invoke(null, new Object[] {new String[0]});
		}
	    catch (Throwable e)
		{
		    e.printStackTrace();
		}
	}
    }

    public Starter(String jarFilename, String className)
	throws Exception
    {
	File jarFile = new File(jarFilename);

	URL jarURL = jarFile.toURL();

	MyURLClassLoader classLoader = new MyURLClassLoader(new
							    URL[] {jarURL});

	Class targetClass = classLoader.loadClass(className);
	Class[] arg_types = { String[].class };
	Method mainMethod = targetClass.getMethod("main", arg_types);

	Thread mainThread = new MainThread(mainMethod);
	mainThread.setContextClassLoader(classLoader);

	mainThread.start();
	mainThread.join();
	System.out.println("Main Method of " + className + " exited");
    }

    public static void main(String[] args) throws Exception
    {
	new Starter(args[0], args[1]);
	System.gc();
	System.in.read(); // block until user hits return
    }
}

-------------------------------------------
Stupid.java:
-------------------------------------------
public class Stupid
{
    public Stupid() throws Exception
    {
	Thread shutdownHook = new Thread()
	    {
		public void run()
		{
		    System.out.println("This is a stupid shutdown hook");
		}
	    };
	Runtime.getRuntime().addShutdownHook(shutdownHook);
	Runtime.getRuntime().removeShutdownHook(shutdownHook);
    }

    public static void main(String[] args) throws Exception
    {
	System.out.println("This is a stupid program");
	new Stupid();
	System.out.println("This has been a stupid program");
    }
}

Comments
EVALUATION This does not look like a bug. The problem is that the shutdown hook is an unstarted thread associated with the classloader. Regardless of whether the shutdown hook is registered or else registered and then de-registered, the unstarted thread remains associated with the urlclassloader, which therefore cannot be finalized. The thread has a reference to its context class loader, and the main thread group has a reference to the unstarted thread. Therefore the classloader instance can never be finialized. It would help to understand some more what exactly the customer is trying to do that depends on the finalizer running. Maybe we can suggest a workaround. ###@###.### 2002-05-14 This is more of a lang issue anyway. It really has nothing to do with networking so I am going to recategorize it. ###@###.### 2002-05-16 The original analysis is correct. This bug is a duplicate of 4533087. See 4197876 for a possible workaround. -- ###@###.### 2002/5/31
16-05-2002