-
Bug
-
Resolution: Duplicate
-
P4
-
None
-
6
-
x86
-
windows_2003
FULL PRODUCT VERSION :
java version "1.6.0_06"
Java(TM) SE Runtime Environment (build 1.6.0_06-b02)
Java HotSpot(TM) Client VM (build 10.0-b22, mixed mode, sharing)
ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows [Version 5.2.3790]
A DESCRIPTION OF THE PROBLEM :
LogManager retains strong references to Loggers.
Logger retains strong reference to ResourceBundle.
When ResourceBundle is subclassed and loaded by a custom class loader, this results in a memory leak because custom class loader is strongly reachable from LogManager.
In environments with dynamic class loaders (such as J2EE) this often leads to infamous OutOfMemoryError: PermGen space exception.
The problem was discovered with Glassfish v2 and Apache MyFaces Trinidad 1.2.8. Trinidad subclasses ListResourceBundle to provide log messages localization.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Execute the provided test case.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Both test methods should succeed.
ACTUAL -
Test method test1() fails.
Test method test2() succeeds.
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
======= MyClassLoader.java =======
import java.net.URLClassLoader;
import java.net.URL;
public class MyClassLoader extends URLClassLoader
{
public MyClassLoader(URL[] urls)
{
super(urls, null);
}
public static MyClassLoader create()
{
URLClassLoader defaultLoader = (URLClassLoader) Thread.currentThread().getContextClassLoader();
URL[] classpath = defaultLoader.getURLs();
return new MyClassLoader(classpath);
}
}
======= MyLogger.java =======
import java.util.logging.Logger;
public class MyLogger extends Logger
{
public MyLogger(String name, String resourceBundleName)
{
super(name, resourceBundleName);
}
}
======= MyResourceBundle1.java =======
import java.util.ListResourceBundle;
public class MyResourceBundle1 extends ListResourceBundle
{
protected Object[][] getContents()
{
return new Object[][]
{
{"test.key", "test value 1"},
};
}
}
======= Test1.java =======
import static junit.framework.Assert.*;
import org.junit.Test;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.util.ResourceBundle;
import java.util.logging.LogManager;
import java.util.logging.Logger;
public class Test1
{
@Test
public void test1() throws Exception
{
// init log manager in default class loader
LogManager.getLogManager();
ClassLoader myClassLoader = MyClassLoader.create();
createLoggerWithTempClassLoader(myClassLoader, "test1", "MyResourceBundle1");
assertSame(myClassLoader, bundleClassLoader(Logger.getLogger("test1").getResourceBundle()));
assertEquals("test value 1", Logger.getLogger("test1").getResourceBundle().getString("test.key"));
myClassLoader = null;
System.gc();
assertNull(bundleClassLoader(Logger.getLogger("test1").getResourceBundle()));
}
@Test
public void test2() throws Exception
{
// init log manager in default class loader
LogManager.getLogManager();
ClassLoader myClassLoader = MyClassLoader.create();
createLoggerWithTempClassLoader(myClassLoader, "test2", "MyResourceBundle2");
assertSame(myClassLoader, bundleClassLoader(Logger.getLogger("test2").getResourceBundle()));
assertEquals("test value 2", Logger.getLogger("test2").getResourceBundle().getString("test.key"));
myClassLoader = null;
System.gc();
assertNull(bundleClassLoader(Logger.getLogger("test2").getResourceBundle()));
}
private void createLoggerWithTempClassLoader(ClassLoader myClassLoader, String loggerName, String bundleName)
{
ClassLoader defaultLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(myClassLoader);
MyLogger logger = new MyLogger(loggerName, bundleName);
assertTrue(LogManager.getLogManager().addLogger(logger));
Thread.currentThread().setContextClassLoader(defaultLoader);
}
private ClassLoader bundleClassLoader(ResourceBundle resourceBundle) throws Exception
{
Object cacheKey = getFieldValue(ResourceBundle.class, resourceBundle, "cacheKey");
WeakReference loaderRef = (WeakReference) getFieldValue(cacheKey, "loaderRef");
return (ClassLoader) loaderRef.get();
}
private Object getFieldValue(Object obj, String fieldName) throws Exception
{
return getFieldValue(obj.getClass(), obj, fieldName);
}
private Object getFieldValue(Class clazz, Object obj, String fieldName) throws Exception
{
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(obj);
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Do not use ResourceBundle subclassing with Java Logging API.
java version "1.6.0_06"
Java(TM) SE Runtime Environment (build 1.6.0_06-b02)
Java HotSpot(TM) Client VM (build 10.0-b22, mixed mode, sharing)
ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows [Version 5.2.3790]
A DESCRIPTION OF THE PROBLEM :
LogManager retains strong references to Loggers.
Logger retains strong reference to ResourceBundle.
When ResourceBundle is subclassed and loaded by a custom class loader, this results in a memory leak because custom class loader is strongly reachable from LogManager.
In environments with dynamic class loaders (such as J2EE) this often leads to infamous OutOfMemoryError: PermGen space exception.
The problem was discovered with Glassfish v2 and Apache MyFaces Trinidad 1.2.8. Trinidad subclasses ListResourceBundle to provide log messages localization.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Execute the provided test case.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Both test methods should succeed.
ACTUAL -
Test method test1() fails.
Test method test2() succeeds.
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
======= MyClassLoader.java =======
import java.net.URLClassLoader;
import java.net.URL;
public class MyClassLoader extends URLClassLoader
{
public MyClassLoader(URL[] urls)
{
super(urls, null);
}
public static MyClassLoader create()
{
URLClassLoader defaultLoader = (URLClassLoader) Thread.currentThread().getContextClassLoader();
URL[] classpath = defaultLoader.getURLs();
return new MyClassLoader(classpath);
}
}
======= MyLogger.java =======
import java.util.logging.Logger;
public class MyLogger extends Logger
{
public MyLogger(String name, String resourceBundleName)
{
super(name, resourceBundleName);
}
}
======= MyResourceBundle1.java =======
import java.util.ListResourceBundle;
public class MyResourceBundle1 extends ListResourceBundle
{
protected Object[][] getContents()
{
return new Object[][]
{
{"test.key", "test value 1"},
};
}
}
======= Test1.java =======
import static junit.framework.Assert.*;
import org.junit.Test;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.util.ResourceBundle;
import java.util.logging.LogManager;
import java.util.logging.Logger;
public class Test1
{
@Test
public void test1() throws Exception
{
// init log manager in default class loader
LogManager.getLogManager();
ClassLoader myClassLoader = MyClassLoader.create();
createLoggerWithTempClassLoader(myClassLoader, "test1", "MyResourceBundle1");
assertSame(myClassLoader, bundleClassLoader(Logger.getLogger("test1").getResourceBundle()));
assertEquals("test value 1", Logger.getLogger("test1").getResourceBundle().getString("test.key"));
myClassLoader = null;
System.gc();
assertNull(bundleClassLoader(Logger.getLogger("test1").getResourceBundle()));
}
@Test
public void test2() throws Exception
{
// init log manager in default class loader
LogManager.getLogManager();
ClassLoader myClassLoader = MyClassLoader.create();
createLoggerWithTempClassLoader(myClassLoader, "test2", "MyResourceBundle2");
assertSame(myClassLoader, bundleClassLoader(Logger.getLogger("test2").getResourceBundle()));
assertEquals("test value 2", Logger.getLogger("test2").getResourceBundle().getString("test.key"));
myClassLoader = null;
System.gc();
assertNull(bundleClassLoader(Logger.getLogger("test2").getResourceBundle()));
}
private void createLoggerWithTempClassLoader(ClassLoader myClassLoader, String loggerName, String bundleName)
{
ClassLoader defaultLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(myClassLoader);
MyLogger logger = new MyLogger(loggerName, bundleName);
assertTrue(LogManager.getLogManager().addLogger(logger));
Thread.currentThread().setContextClassLoader(defaultLoader);
}
private ClassLoader bundleClassLoader(ResourceBundle resourceBundle) throws Exception
{
Object cacheKey = getFieldValue(ResourceBundle.class, resourceBundle, "cacheKey");
WeakReference loaderRef = (WeakReference) getFieldValue(cacheKey, "loaderRef");
return (ClassLoader) loaderRef.get();
}
private Object getFieldValue(Object obj, String fieldName) throws Exception
{
return getFieldValue(obj.getClass(), obj, fieldName);
}
private Object getFieldValue(Class clazz, Object obj, String fieldName) throws Exception
{
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(obj);
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Do not use ResourceBundle subclassing with Java Logging API.
- duplicates
-
JDK-6274920 JDK logger holds strong reference to java.util.logging.Logger instances
- Closed