FileDocCategorySizeDatePackage
WeakHashtable.javaAPI DocAndroid 1.5 API16492Wed May 06 22:41:10 BST 2009org.apache.commons.logging.impl

WeakHashtable

public final class WeakHashtable extends Hashtable

Implementation of Hashtable that uses WeakReference's to hold its keys thus allowing them to be reclaimed by the garbage collector. The associated values are retained using strong references.

This class follows the symantics of Hashtable as closely as possible. It therefore does not accept null values or keys.

Note: This is not intended to be a general purpose hash table replacement. This implementation is also tuned towards a particular purpose: for use as a replacement for Hashtable in LogFactory. This application requires good liveliness for get and put. Various tradeoffs have been made with this in mind.

Usage: typical use case is as a drop-in replacement for the Hashtable used in LogFactory for J2EE enviroments running 1.3+ JVMs. Use of this class in most cases (see below) will allow classloaders to be collected by the garbage collector without the need to call {@link org.apache.commons.logging.LogFactory#release(ClassLoader) LogFactory.release(ClassLoader)}.

org.apache.commons.logging.LogFactory checks whether this class can be supported by the current JVM, and if so then uses it to store references to the LogFactory implementationd it loads (rather than using a standard Hashtable instance). Having this class used instead of Hashtable solves certain issues related to dynamic reloading of applications in J2EE-style environments. However this class requires java 1.3 or later (due to its use of java.lang.ref.WeakReference and associates). And by the way, this extends Hashtable rather than HashMap for backwards compatibility reasons. See the documentation for method LogFactory.createFactoryStore for more details.

The reason all this is necessary is due to a issue which arises during hot deploy in a J2EE-like containers. Each component running in the container owns one or more classloaders; when the component loads a LogFactory instance via the component classloader a reference to it gets stored in the static LogFactory.factories member, keyed by the component's classloader so different components don't stomp on each other. When the component is later unloaded, the container sets the component's classloader to null with the intent that all the component's classes get garbage-collected. However there's still a reference to the component's classloader from a key in the "global" LogFactory's factories member! If LogFactory.release() is called whenever component is unloaded, the classloaders will be correctly garbage collected; this should be done by any container that bundles commons-logging by default. However, holding the classloader references weakly ensures that the classloader will be garbage collected without the container performing this step.

Limitations: There is still one (unusual) scenario in which a component will not be correctly unloaded without an explicit release. Though weak references are used for its keys, it is necessary to use strong references for its values.

If the abstract class LogFactory is loaded by the container classloader but a subclass of LogFactory [LogFactory1] is loaded by the component's classloader and an instance stored in the static map associated with the base LogFactory class, then there is a strong reference from the LogFactory class to the LogFactory1 instance (as normal) and a strong reference from the LogFactory1 instance to the component classloader via getClass().getClassLoader(). This chain of references will prevent collection of the child classloader.

Such a situation occurs when the commons-logging.jar is loaded by a parent classloader (e.g. a server level classloader in a servlet container) and a custom LogFactory implementation is loaded by a child classloader (e.g. a web app classloader).

To avoid this scenario, ensure that any custom LogFactory subclass is loaded by the same classloader as the base LogFactory. Creating custom LogFactory subclasses is, however, rare. The standard LogFactoryImpl class should be sufficient for most or all users.

author
Brian Stansberry
since
1.1

Fields Summary
private static final int
MAX_CHANGES_BEFORE_PURGE
The maximum number of times put() or remove() can be called before the map will be purged of all cleared entries.
private static final int
PARTIAL_PURGE_COUNT
The maximum number of times put() or remove() can be called before the map will be purged of one cleared entry.
private ReferenceQueue
queue
private int
changeCount
Constructors Summary
public WeakHashtable()
Constructs a WeakHashtable with the Hashtable default capacity and load factor.

    
                    
      
Methods Summary
public booleancontainsKey(java.lang.Object key)

see
Hashtable

        // purge should not be required
        Referenced referenced = new Referenced(key);
        return super.containsKey(referenced);
    
public java.util.Enumerationelements()

see
Hashtable

        purge();
        return super.elements();
    
public java.util.SetentrySet()

see
Hashtable

        purge();
        Set referencedEntries = super.entrySet();
        Set unreferencedEntries = new HashSet();
        for (Iterator it=referencedEntries.iterator(); it.hasNext();) {
            Map.Entry entry = (Map.Entry) it.next();
            Referenced referencedKey = (Referenced) entry.getKey();
            Object key = referencedKey.getValue();
            Object value = entry.getValue();
            if (key != null) {
                Entry dereferencedEntry = new Entry(key, value);
                unreferencedEntries.add(dereferencedEntry);
            }
        }
        return unreferencedEntries;
    
public java.lang.Objectget(java.lang.Object key)

see
Hashtable

        // for performance reasons, no purge
        Referenced referenceKey = new Referenced(key);
        return super.get(referenceKey);
    
public booleanisEmpty()

see
Hashtable

        purge();
        return super.isEmpty();
    
public java.util.SetkeySet()

see
Hashtable

        purge();
        Set referencedKeys = super.keySet();
        Set unreferencedKeys = new HashSet();
        for (Iterator it=referencedKeys.iterator(); it.hasNext();) {
            Referenced referenceKey = (Referenced) it.next();
            Object keyValue = referenceKey.getValue();
            if (keyValue != null) {
                unreferencedKeys.add(keyValue);
            }
        }
        return unreferencedKeys;
    
public java.util.Enumerationkeys()

see
Hashtable

        purge();
        final Enumeration enumer = super.keys();
        return new Enumeration() {
            public boolean hasMoreElements() {
                return enumer.hasMoreElements();
            }
            public Object nextElement() {
                 Referenced nextReference = (Referenced) enumer.nextElement();
                 return nextReference.getValue();
            }
        };
    
private voidpurge()
Purges all entries whose wrapped keys have been garbage collected.

        synchronized (queue) {
            WeakKey key;
            while ((key = (WeakKey) queue.poll()) != null) {
                super.remove(key.getReferenced());
            }
        }
    
private voidpurgeOne()
Purges one entry whose wrapped key has been garbage collected.

        
        synchronized (queue) {
            WeakKey key = (WeakKey) queue.poll();
            if (key != null) {
                super.remove(key.getReferenced());
            }
        }
    
public java.lang.Objectput(java.lang.Object key, java.lang.Object value)

see
Hashtable

        // check for nulls, ensuring symantics match superclass
        if (key == null) {
            throw new NullPointerException("Null keys are not allowed");
        }
        if (value == null) {
            throw new NullPointerException("Null values are not allowed");
        }

        // for performance reasons, only purge every 
        // MAX_CHANGES_BEFORE_PURGE times
        if (changeCount++ > MAX_CHANGES_BEFORE_PURGE) {
            purge();
            changeCount = 0;
        }
        // do a partial purge more often
        else if ((changeCount % PARTIAL_PURGE_COUNT) == 0) {
            purgeOne();
        }
        
        Object result = null;
        Referenced keyRef = new Referenced(key, queue);
        return super.put(keyRef, value);
    
public voidputAll(java.util.Map t)

see
Hashtable

        if (t != null) {
            Set entrySet = t.entrySet();
            for (Iterator it=entrySet.iterator(); it.hasNext();) {
                Map.Entry entry = (Map.Entry) it.next();
                put(entry.getKey(), entry.getValue());
            }
        }
    
protected voidrehash()

see
Hashtable

        // purge here to save the effort of rehashing dead entries
        purge();
        super.rehash();
    
public java.lang.Objectremove(java.lang.Object key)

see
Hashtable

        // for performance reasons, only purge every 
        // MAX_CHANGES_BEFORE_PURGE times
        if (changeCount++ > MAX_CHANGES_BEFORE_PURGE) {
            purge();
            changeCount = 0;
        }
        // do a partial purge more often
        else if ((changeCount % PARTIAL_PURGE_COUNT) == 0) {
            purgeOne();
        }
        return super.remove(new Referenced(key));
    
public intsize()

see
Hashtable

        purge();
        return super.size();
    
public java.lang.StringtoString()

see
Hashtable

        purge();
        return super.toString();
    
public java.util.Collectionvalues()

see
Hashtable

        purge();
        return super.values();