Improvements to WeakHashTable. Values are now held with hard references and a reference queue is polled during a purge. Contributed by Brian Stansberry.
git-svn-id: https://svn.apache.org/repos/asf/jakarta/commons/proper/logging/trunk@139059 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
@@ -17,8 +17,12 @@
|
||||
|
||||
package org.apache.commons.logging;
|
||||
|
||||
import junit.framework.*;
|
||||
import java.util.*;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Hashtable;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.apache.commons.logging.impl.LogFactoryImpl;
|
||||
import org.apache.commons.logging.impl.WeakHashtable;
|
||||
|
||||
public class LogFactoryTest extends TestCase {
|
||||
@@ -27,6 +31,8 @@ public class LogFactoryTest extends TestCase {
|
||||
/** Maximum number of iterations before our test fails */
|
||||
private static final int MAX_GC_ITERATIONS = 50;
|
||||
|
||||
private ClassLoader origLoader = null;
|
||||
private String origFactoryProperty = null;
|
||||
|
||||
public LogFactoryTest(String testName) {
|
||||
super(testName);
|
||||
@@ -35,4 +41,170 @@ public class LogFactoryTest extends TestCase {
|
||||
public void testLogFactoryType() {
|
||||
assertTrue(LogFactory.factories instanceof WeakHashtable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that LogFactories are not removed from the map
|
||||
* if their creating ClassLoader is still alive.
|
||||
*/
|
||||
public void testHoldFactories()
|
||||
{
|
||||
// Get a factory and create a WeakReference to it that
|
||||
// we can check to see if the factory has been removed
|
||||
// from LogFactory.properties
|
||||
LogFactory factory = LogFactory.getFactory();
|
||||
WeakReference weakFactory = new WeakReference(factory);
|
||||
|
||||
// Remove any hard reference to the factory
|
||||
factory = null;
|
||||
|
||||
// Run the gc, confirming that the original factory
|
||||
// is not dropped from the map even though there are
|
||||
// no other references to it
|
||||
int iterations = 0;
|
||||
int bytz = 2;
|
||||
while(iterations++ < MAX_GC_ITERATIONS) {
|
||||
System.gc();
|
||||
|
||||
assertNotNull("LogFactory released while ClassLoader still active.",
|
||||
weakFactory.get());
|
||||
|
||||
// create garbage:
|
||||
byte[] b;
|
||||
try {
|
||||
b = new byte[bytz];
|
||||
bytz = bytz * 2;
|
||||
}
|
||||
catch (OutOfMemoryError oom) {
|
||||
// This error means the test passed, as it means the LogFactory
|
||||
// was never released. So, we have to catch and deal with it
|
||||
|
||||
// Doing this is probably a no-no, but it seems to work ;-)
|
||||
b = null;
|
||||
System.gc();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a LogFactory is eventually removed from the map
|
||||
* after its creating ClassLoader is garbage collected.
|
||||
*/
|
||||
public void testReleaseFactories()
|
||||
{
|
||||
// Create a temporary classloader
|
||||
ClassLoader childLoader = new ClassLoader() {};
|
||||
Thread.currentThread().setContextClassLoader(childLoader);
|
||||
|
||||
// Get a factory using the child loader.
|
||||
LogFactory factory = LogFactory.getFactory();
|
||||
// Hold a WeakReference to the factory. When this reference
|
||||
// is cleared we know the factory has been cleared from
|
||||
// LogFactory.factories as well
|
||||
WeakReference weakFactory = new WeakReference(factory);
|
||||
|
||||
// Get a WeakReference to the child loader so we know when it
|
||||
// has been gc'ed
|
||||
WeakReference weakLoader = new WeakReference(childLoader);
|
||||
|
||||
// Remove any hard reference to the childLoader and the factory
|
||||
Thread.currentThread().setContextClassLoader(origLoader);
|
||||
childLoader = null;
|
||||
factory = null;
|
||||
|
||||
// Run the gc, confirming that the original childLoader
|
||||
// is dropped from the map
|
||||
int iterations = 0;
|
||||
int bytz = 2;
|
||||
while(true) {
|
||||
System.gc();
|
||||
if(iterations++ > MAX_GC_ITERATIONS){
|
||||
fail("Max iterations reached before childLoader released.");
|
||||
}
|
||||
|
||||
if(weakLoader.get() == null) {
|
||||
break;
|
||||
} else {
|
||||
// create garbage:
|
||||
byte[] b;
|
||||
try {
|
||||
b = new byte[bytz];
|
||||
bytz = bytz * 2;
|
||||
}
|
||||
catch (OutOfMemoryError oom) {
|
||||
// Doing this is probably a no-no, but it seems to work ;-)
|
||||
b = null;
|
||||
System.gc();
|
||||
fail("OutOfMemory before childLoader released.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Confirm that the original factory is removed from the map
|
||||
// within the maximum allowed number of calls to put() +
|
||||
// the maximum number of subsequent gc iterations
|
||||
iterations = 0;
|
||||
while(true) {
|
||||
System.gc();
|
||||
if(iterations++ > WeakHashtable.MAX_PUTS_BEFORE_PURGE + MAX_GC_ITERATIONS){
|
||||
Hashtable table = LogFactory.factories;
|
||||
fail("Max iterations reached before factory released.");
|
||||
}
|
||||
|
||||
// Create a new child loader and use it to add to the map.
|
||||
ClassLoader newChildLoader = new ClassLoader() {};
|
||||
Thread.currentThread().setContextClassLoader(newChildLoader);
|
||||
LogFactory.getFactory();
|
||||
Thread.currentThread().setContextClassLoader(origLoader);
|
||||
|
||||
if(weakFactory.get() == null) {
|
||||
break;
|
||||
} else {
|
||||
// create garbage:
|
||||
byte[] b;
|
||||
try {
|
||||
b = new byte[bytz];
|
||||
bytz = bytz * 2;
|
||||
}
|
||||
catch (OutOfMemoryError oom) {
|
||||
// Doing this is probably a no-no, but it seems to work ;-)
|
||||
b = null;
|
||||
bytz = 2; // start over
|
||||
System.gc();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected void setUp() throws Exception {
|
||||
// Preserve the original classloader and factory implementation
|
||||
// class so we can restore them when we are done
|
||||
origLoader = Thread.currentThread().getContextClassLoader();
|
||||
origFactoryProperty = System.getProperty(LogFactory.FACTORY_PROPERTY);
|
||||
|
||||
// Ensure we use LogFactoryImpl as our factory
|
||||
System.setProperty(LogFactory.FACTORY_PROPERTY,
|
||||
LogFactoryImpl.class.getName());
|
||||
|
||||
super.setUp();
|
||||
}
|
||||
|
||||
protected void tearDown() throws Exception {
|
||||
// Set the classloader back to whatever it originally was
|
||||
Thread.currentThread().setContextClassLoader(origLoader);
|
||||
|
||||
// Set the factory implementation class back to
|
||||
// whatever it originally was
|
||||
if (origFactoryProperty != null) {
|
||||
System.setProperty(LogFactory.FACTORY_PROPERTY,
|
||||
origFactoryProperty);
|
||||
}
|
||||
else {
|
||||
System.getProperties().remove(LogFactory.FACTORY_PROPERTY);
|
||||
}
|
||||
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
package org.apache.commons.logging.impl;
|
||||
|
||||
import java.lang.ref.*;
|
||||
import junit.framework.*;
|
||||
import java.util.*;
|
||||
|
||||
@@ -206,7 +207,10 @@ public class WeakHashtableTest extends TestCase {
|
||||
}
|
||||
|
||||
public void testRelease() throws Exception {
|
||||
|
||||
assertNotNull(weakHashtable.get(new Long(1)));
|
||||
ReferenceQueue testQueue = new ReferenceQueue();
|
||||
WeakReference weakKeyOne = new WeakReference(keyOne, testQueue);
|
||||
|
||||
// lose our references
|
||||
keyOne = null;
|
||||
keyTwo = null;
|
||||
@@ -233,6 +237,12 @@ public class WeakHashtableTest extends TestCase {
|
||||
}
|
||||
}
|
||||
|
||||
// some JVMs seem to take a little time to put references on
|
||||
// the reference queue once the reference has been collected
|
||||
// need to think about whether this is enough to justify
|
||||
// stepping through the collection each time...
|
||||
while(testQueue.poll() == null) {}
|
||||
|
||||
// Test that the released objects are not taking space in the table
|
||||
assertEquals("underlying table not emptied", 0, weakHashtable.size());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user