United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
Bug ID: 7008595 Class loader leak caused by keepAliveTimer thread in KeepAliveCache
7008595 : Class loader leak caused by keepAliveTimer thread in KeepAliveCache

Details
Type:
Bug
Submit Date:
2010-12-22
Status:
Closed
Updated Date:
2011-03-10
Project Name:
JDK
Resolved Date:
2011-03-10
Component:
core-libs
OS:
generic
Sub-Component:
java.net
CPU:
generic
Priority:
P3
Resolution:
Fixed
Affected Versions:
7
Fixed Versions:
7

Related Reports

Sub Tasks

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
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
                                     
2011-02-03
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;
                     }
                                     
2011-02-03
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.
                                     
2010-12-22



Hardware and Software, Engineered to Work Together