All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.commons.logging.impl.WeakHashtable Maven / Gradle / Ivy

There is a newer version: 4.0.4
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.commons.logging.impl;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 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 semantics 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 environments * 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 implementation 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. * * @version $Id: WeakHashtable.java 1435077 2013-01-18 10:51:35Z tn $ * @since 1.1 */ public final class WeakHashtable extends Hashtable { /** Serializable version identifier. */ private static final long serialVersionUID = -1546036869799732453L; /** * 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 MAX_CHANGES_BEFORE_PURGE = 100; /** * The maximum number of times put() or remove() can be called before * the map will be purged of one cleared entry. */ private static final int PARTIAL_PURGE_COUNT = 10; /* ReferenceQueue we check for gc'd keys */ private final ReferenceQueue queue = new ReferenceQueue(); /* Counter used to control how often we purge gc'd entries */ private int changeCount = 0; /** * Constructs a WeakHashtable with the Hashtable default * capacity and load factor. */ public WeakHashtable() {} /** *@see Hashtable */ public boolean containsKey(Object key) { // purge should not be required Referenced referenced = new Referenced(key); return super.containsKey(referenced); } /** *@see Hashtable */ public Enumeration elements() { purge(); return super.elements(); } /** *@see Hashtable */ public Set entrySet() { 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; } /** *@see Hashtable */ public Object get(Object key) { // for performance reasons, no purge Referenced referenceKey = new Referenced(key); return super.get(referenceKey); } /** *@see Hashtable */ public Enumeration keys() { 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(); } }; } /** *@see Hashtable */ public Set keySet() { 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; } /** *@see Hashtable */ public synchronized Object put(Object key, Object value) { // check for nulls, ensuring semantics 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(); } Referenced keyRef = new Referenced(key, queue); return super.put(keyRef, value); } /** *@see Hashtable */ public void putAll(Map t) { 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()); } } } /** *@see Hashtable */ public Collection values() { purge(); return super.values(); } /** *@see Hashtable */ public synchronized Object remove(Object key) { // 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)); } /** *@see Hashtable */ public boolean isEmpty() { purge(); return super.isEmpty(); } /** *@see Hashtable */ public int size() { purge(); return super.size(); } /** *@see Hashtable */ public String toString() { purge(); return super.toString(); } /** * @see Hashtable */ protected void rehash() { // purge here to save the effort of rehashing dead entries purge(); super.rehash(); } /** * Purges all entries whose wrapped keys * have been garbage collected. */ private void purge() { final List toRemove = new ArrayList(); synchronized (queue) { WeakKey key; while ((key = (WeakKey) queue.poll()) != null) { toRemove.add(key.getReferenced()); } } // LOGGING-119: do the actual removal of the keys outside the sync block // to prevent deadlock scenarios as purge() may be called from // non-synchronized methods too final int size = toRemove.size(); for (int i = 0; i < size; i++) { super.remove(toRemove.get(i)); } } /** * Purges one entry whose wrapped key * has been garbage collected. */ private void purgeOne() { synchronized (queue) { WeakKey key = (WeakKey) queue.poll(); if (key != null) { super.remove(key.getReferenced()); } } } /** Entry implementation */ private final static class Entry implements Map.Entry { private final Object key; private final Object value; private Entry(Object key, Object value) { this.key = key; this.value = value; } public boolean equals(Object o) { boolean result = false; if (o != null && o instanceof Map.Entry) { Map.Entry entry = (Map.Entry) o; result = (getKey()==null ? entry.getKey() == null : getKey().equals(entry.getKey())) && (getValue()==null ? entry.getValue() == null : getValue().equals(entry.getValue())); } return result; } public int hashCode() { return (getKey()==null ? 0 : getKey().hashCode()) ^ (getValue()==null ? 0 : getValue().hashCode()); } public Object setValue(Object value) { throw new UnsupportedOperationException("Entry.setValue is not supported."); } public Object getValue() { return value; } public Object getKey() { return key; } } /** Wrapper giving correct symantics for equals and hashcode */ private final static class Referenced { private final WeakReference reference; private final int hashCode; /** * * @throws NullPointerException if referant is null */ private Referenced(Object referant) { reference = new WeakReference(referant); // Calc a permanent hashCode so calls to Hashtable.remove() // work if the WeakReference has been cleared hashCode = referant.hashCode(); } /** * * @throws NullPointerException if key is null */ private Referenced(Object key, ReferenceQueue queue) { reference = new WeakKey(key, queue, this); // Calc a permanent hashCode so calls to Hashtable.remove() // work if the WeakReference has been cleared hashCode = key.hashCode(); } public int hashCode() { return hashCode; } private Object getValue() { return reference.get(); } public boolean equals(Object o) { boolean result = false; if (o instanceof Referenced) { Referenced otherKey = (Referenced) o; Object thisKeyValue = getValue(); Object otherKeyValue = otherKey.getValue(); if (thisKeyValue == null) { result = otherKeyValue == null; // Since our hashcode was calculated from the original // non-null referant, the above check breaks the // hashcode/equals contract, as two cleared Referenced // objects could test equal but have different hashcodes. // We can reduce (not eliminate) the chance of this // happening by comparing hashcodes. result = result && this.hashCode() == otherKey.hashCode(); // In any case, as our c'tor does not allow null referants // and Hashtable does not do equality checks between // existing keys, normal hashtable operations should never // result in an equals comparison between null referants } else { result = thisKeyValue.equals(otherKeyValue); } } return result; } } /** * WeakReference subclass that holds a hard reference to an * associated value and also makes accessible * the Referenced object holding it. */ private final static class WeakKey extends WeakReference { private final Referenced referenced; private WeakKey(Object key, ReferenceQueue queue, Referenced referenced) { super(key, queue); this.referenced = referenced; } private Referenced getReferenced() { return referenced; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy