JDK-4950148 : (cl) ClassLoader should have an explicit disposal mechanism
  • Type: Enhancement
  • Component: core-libs
  • Sub-Component: java.lang:class_loading
  • Affected Version: 1.4.2,6
  • Priority: P4
  • Status: Closed
  • Resolution: Fixed
  • OS: generic,windows_2000
  • CPU: generic,x86
  • Submitted: 2003-11-06
  • Updated: 2024-01-01
  • Resolved: 2024-01-01
Related Reports
Relates :  
Relates :  
Relates :  
Description
Name: rmT116609			Date: 11/06/2003


A DESCRIPTION OF THE REQUEST :
At present there is no explicit end-of-lifecycle support on the ClassLoader interface. As a result, if the ClassLoader holds open files, sockets, etc. as a performance optimisation there is no clear way of indicating that the use of the ClassLoader is complete.

An example of this is the URLClassLoader - if one of the URLs is a jar: URL then as soon a class has been loaded (or a resource accessed) by the URLClassLoader the JAR file is left open until the JarFile object (allocated by sun.misc.URLClassPath) is garbage collected.

On Windows 2000 this manifests itself as a file lock which prevents the file being deleted. This makes reasonable sense while the ClassLoader is in use since replacing the file at run-time could lead to strange behaviour.

However, if an application can guarantee that the ClassLoader is no longer in use (or at least guarantee that it should no longer be used to load any further resources or classes) it would make sense to allow an application to dispose of the ClassLoader explicitly, closing any resources held by the ClassLoader. E.g. by adding a dispose() method to the ClassLoader class.

Without an explicit mechanism for disposing of ClassLoaders there are problems associated with an application cleaning up the resources being accessed by the ClassLoader.

At present there is no explicit end-of-lifecycle support on the ClassLoader interface. As a result, if the ClassLoader holds open files, sockets, etc. as a performance optimisation there is no clear way of indicating that the use of the ClassLoader is complete. Consequently, the files remain open and locked until the VM completes.

An example of this is the URLClassLoader - if one of the URLs is a jar: URL then as soon a class has been loaded (or a resource accessed) by the URLClassLoader the JAR file is left open even after the JarFile object (allocated by sun.misc.URLClassPath) is garbage collected.

On Windows 2000 this manifests itself as a file lock which prevents the file being deleted even from within the VM. This makes reasonable sense while the ClassLoader is in use since replacing the file at run-time could lead to strange behaviour.

However, if an application can guarantee that the ClassLoader is no longer in use (or at least indicate that it should no longer be used to load any further resources or classes) it would make sense to allow an application to dispose of the ClassLoader explicitly, closing any resources held by the ClassLoader. E.g. by adding a dispose() method to the ClassLoader class.

Without an explicit mechanism for disposing of ClassLoaders there are problems associated with an application cleaning up the resources being accessed by a custom ClassLoader.

For example:
1) An application creates a temporary JAR file
2) The application creates a URLClassLoader to access the classes/resources contained within the JAR file
3) The application finished using the URLClassLoader and sets all references to the ClassLoader and instances of classes loaded by the ClassLoader to null.
4) The application can now *not* delete the temporary JAR file until the URLClassLoader is garbage collected. Since there is no way of forcing garbage collection in Java the application can never guarantee deletion of the files.

Using File.deleteOnExit() doesn't appear to work to delete these files either.


JUSTIFICATION :
The current ClassLoader interface/contract implicitly forces implementations to rely on garbage collection for cleaning up any resources it holds (which doesn't always work).


EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
ClassLoader should provide a method such as dispose() that prevents the ClassLoader from being used to access new resources/classes.

Alternatively a method that indicates a ClassLoader should flush resources held until a new resource/class load request occurs would also be acceptable.

ACTUAL -
ClassLoaders such as URLClassLoader only free up their resources (such as open JarFile instances) on garbage collection through the normal finalization mechanism (which doesn't actually work in some cases).



---------- BEGIN SOURCE ----------
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class ClassLoaderDemo {

    private final static String RESOURCE_NAME = "resource.txt";

    public static void main(String[] args) throws Exception {
        System.out.println("Java version=[" + System.getProperty("java.version") + "]");
        
        // Create a zip file with a file called resource.txt in it
        File f = createZipFile();
        
        // Look - it gets deleted when I haven't pointed the classloader at it
        deleteFile(0, f);
        
        // Recreate the file as I seem to have deleted it
        f = createZipFile();
        
        // Create classloader that uses the zip file
        URL jarUrl = f.toURL();
        ClassLoader classLoader = new NoisyUrlClassLoader(new URL[] { jarUrl });
        
        // Load resource.txt from the zip file using the classloader
        InputStream is = classLoader.getResourceAsStream(RESOURCE_NAME);
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        System.out.println("Read from resource: " + br.readLine());
        br.close();
        
        // I cannot indicate I have finished with the ClassLoader!
        // classLoader.dispose();
        // classLoader.freeResources();
        // classLoader.dieDieDie();

        // Do various things to try to delete the file
        deleteFile(1, f);

        // Try making the classloader eligable for GC
        classLoader = null;
        // I'm not hanging on to anything returned by the ClassLoader
        // here or that references things returned by the classloader
        is = null;
        br = null;
        
        // Try now...
        deleteFile(2, f);
        
        // Poke the garbage collector - this causes the classloader to be collected on my machine
        // with Java 1.3.1_09
        System.gc();
        
        // But still can't delete it
        deleteFile(3, f);

        if (f.exists()) {
            System.out.println("You're now the proud owner of a file called=[" + f + "]");
        }

        // And I bet deleteOnExit doesn't work either....
    }

    private static boolean deleteFile(int count, File f) {
        // Attempt to delete the Jar file
        boolean deletedFile = f.delete();
        if (deletedFile) {
            System.out.println("Successfully delected file=[" + f + "], count=[" + count + "]");
        }
        else {
            System.out.println("Unable to delete file=[" + f + "], count=[" + count + "]");
        }
        return deletedFile;
    }

    private static File createZipFile() throws IOException {
        // Create a Jar/Zip file with a single entry
        File f = File.createTempFile("temp", ".jar");
        
        // Try to make the VM delete the file on exit (it doesn't work though)
        f.deleteOnExit();
        
        FileOutputStream fos = new FileOutputStream(f);
        ZipOutputStream zos = new ZipOutputStream(fos);
        ZipEntry ze = new ZipEntry(RESOURCE_NAME);
        zos.putNextEntry(ze);
        OutputStreamWriter osw = new OutputStreamWriter(zos);
        osw.write("Testing 1,2,3");
        osw.flush();
        zos.closeEntry();
        osw.close();

        System.out.println("Created file=[" + f + "]");

        return f;
    }

    // URLClassLoader that shouts about being garbage collected
    private static class NoisyUrlClassLoader extends URLClassLoader {
        public NoisyUrlClassLoader(URL [] url) {
            super(url);
        }
        
        protected void finalize() throws Throwable {
            System.out.println(">>>>NoisyUrlClassloader is being collected");
            super.finalize();
        }

    }
}



---------- END SOURCE ----------
(Incident Review ID: 223861) 
======================================================================

Comments
Closing this as fixed since URLClassLoader.close was added in JDK7
01-01-2024

EVALUATION This issue has been addressed (at least for URLClassLoader) already in jdk7. See 4167874. URLClassLoader is the most common type of class loader which needs this mechanism. But the API could be promoted to ClassLoader, possibly in the future, but not for jdk7. So, I am going to untarget this report from 7.
28-04-2009

EVALUATION It's too late to consider this for Tiger. -- iag@sfbay 2003-11-06
06-11-2003