JDK-7008595 : Class loader leak caused by keepAliveTimer thread in KeepAliveCache
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.net
  • Affected Version: 7
  • Priority: P3
  • Status: Closed
  • Resolution: Fixed
  • OS: generic
  • CPU: generic
  • Submitted: 2010-12-22
  • Updated: 2016-07-04
  • Resolved: 2011-03-10
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 6 JDK 7
6u121Fixed 7 b130Fixed
Description
SYNOPSIS
--------
Class loader leak caused by keepAliveTimer thread in KeepAliveCache

OPERATING SYSTEM
----------------
All

FULL JDK VERSION
----------------
All (1.4.2, 5.0, 6 and 7)

PROBLEM DESCRIPTION from LICENSEE
---------------------------------
The "keepAliveTimer" daemon thread created in sun.net.www.http.KeepAliveCache inherits the context class loader of the parent thread, and therefore holds a reference to that class loader.

This is fine if the thread's context class loader is the system class loader, but it's bad if the context class loader is a custom class loader that may need to be unloaded at some future point (e.g. in the context of an appserver). The reference held by this daemon thread means that the class loader can never become eligible for GC.

SUGGESTED FIX from LICENSEE
---------------------------
The solution is to set the daemon thread's context class loader to null when it is created (in KeepAliveCache.put()). A similar solution is already used in java.io.StreamCloser.addToQueue().

WORKAROUND
----------
None

REPRODUCTION INSTRUCTIONS
-------------------------
1. javac KeepAliveCacheTest.java
2. java -XX:+HeapDumpOnCtrlBreak KeepAliveCacheTest

Take a heapdump when prompted. Open the heapdump (e.g. in MAT) and look for a byte array of 20MB owned by the leaked class loader. It can easily be seen that the class loader is being retained by the "keepAliveTimer" thread.

TESTCASE
--------
import java.lang.reflect.*;
import java.net.*;
import java.io.*;

public class KeepAliveCacheTest {
    public static void main (String args[]) throws Exception {
        MyClassLoader myCL = new MyClassLoader();

        Thread.currentThread().setContextClassLoader(myCL);

        Class appClass = myCL.loadClass("ApplicationClass", new File("ApplicationClass.class"));
        Method createCacheMethod = appClass.getDeclaredMethod("getConnection", (Class[])null);
        createCacheMethod.setAccessible(true);
        createCacheMethod.invoke(null);
       
        // Destroy all our references to the application Class and its Class loader
        Thread.currentThread().setContextClassLoader(null);
        myCL = null;
        createCacheMethod = null;
        appClass = null;

        // The application Class and its Class loader should now be eligible for GC
        // Let's try to clear them away...
        System.gc();

        System.out.println("Finished. Take heapdump now");
        Thread.sleep(Long.MAX_VALUE);
    }
}

class MyClassLoader extends ClassLoader {
    public Class loadClass(String className, File classFile) throws Exception {
        FileInputStream is = new FileInputStream(classFile);

        byte[] classBytes = new byte[(int)classFile.length()];
        int data;
        int count = 0;
        while ((data = is.read()) != -1) {
            classBytes[count] = (byte)data;
            count++;
        }

        return defineClass(className, classBytes, 0, classBytes.length);
    }
}

class ApplicationClass {
    static byte[] bytes;

    public static void getConnection() throws Exception {
        // create 20MB byte array to highlight the leak
        bytes = new byte[20971520];
       
        URL url = new URL("http://www.google.com");

        // Open a URLConnection, read the InputStream and close it
        // This should ensure that a KeepAliveCache is created
        InputStream is = url.openConnection().getInputStream();
        while (is.read() != -1) {}
        is.close();
    }
}

Comments
SUGGESTED FIX rialto : hg diff src/share/classes/sun/net/www/http/KeepAliveCache.java hg diff diff -r 25462d7eee24 src/share/classes/sun/net/www/http/KeepAliveCache.java --- a/src/share/classes/sun/net/www/http/KeepAliveCache.java Wed Feb 02 13:13:34 2011 -0500 +++ b/src/share/classes/sun/net/www/http/KeepAliveCache.java Thu Feb 03 10:09:35 2011 +0000 @@ -106,6 +106,9 @@ public class KeepAliveCache keepAliveTimer = new Thread(grp, cache, "Keep-Alive-Timer"); keepAliveTimer.setDaemon(true); keepAliveTimer.setPriority(Thread.MAX_PRIORITY - 2); + // Set the context class loader to null in order to avoid + // keeping a strong reference to an application classloader. + keepAliveTimer.setContextClassLoader(null); keepAliveTimer.start(); return null; } rialto : src/share/classes/sun/net/www/http/KeepAliveStream.java diff -r 25462d7eee24 src/share/classes/sun/net/www/http/KeepAliveStream.java --- a/src/share/classes/sun/net/www/http/KeepAliveStream.java Wed Feb 02 13:13:34 2011 -0500 +++ b/src/share/classes/sun/net/www/http/KeepAliveStream.java Thu Feb 03 10:09:40 2011 +0000 @@ -185,6 +185,9 @@ class KeepAliveStream extends MeteredStr cleanerThread = new Thread(grp, queue, "Keep-Alive-SocketCleaner"); cleanerThread.setDaemon(true); cleanerThread.setPriority(Thread.MAX_PRIORITY - 2); + // Set the context class loader to null in order to avoid + // keeping a strong reference to an application classloader. + cleanerThread.setContextClassLoader(null); cleanerThread.start(); return null; }
03-02-2011

EVALUATION Changeset: 3c86f24f7500 Author: chegar Date: 2011-02-03 10:10 +0000 URL: http://hg.openjdk.java.net/jdk7/tl/jdk/rev/3c86f24f7500 7008595: Class loader leak caused by keepAliveTimer thread in KeepAliveCache Reviewed-by: michaelm ! src/share/classes/sun/net/www/http/KeepAliveCache.java ! src/share/classes/sun/net/www/http/KeepAliveStream.java
03-02-2011

EVALUATION The proposed solution of setting the thread's context classloader to null does seem reasonable. I don't see a problem with implementing this solution, but we will need to ensure that only system classes are loaded from the keepAliveTimer thread, which I think is the case.
22-12-2010