WeakHashtable xref

View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17
    18
    19  package org.apache.commons.logging.impl;
    20
    21  import java.lang.ref.ReferenceQueue;
    22  import java.lang.ref.WeakReference;
    23  import java.util.*;
    24
    25  /**
    26   * <p>Implementation of <code>Hashtable</code> that uses <code>WeakReference</code>'s
    27   * to hold its keys thus allowing them to be reclaimed by the garbage collector.
    28   * The associated values are retained using strong references.</p>
    29   *
    30   * <p>This class follows the symantics of <code>Hashtable</code> as closely as
    31   * possible. It therefore does not accept null values or keys.</p>
    32   *
    33   * <p><strong>Note:</strong>
    34   * This is <em>not</em> intended to be a general purpose hash table replacement.
    35   * This implementation is also tuned towards a particular purpose: for use as a replacement
    36   * for <code>Hashtable</code> in <code>LogFactory</code>. This application requires
    37   * good liveliness for <code>get</code> and <code>put</code>. Various tradeoffs
    38   * have been made with this in mind.
    39   * </p>
    40   * <p>
    41   * <strong>Usage:</strong> typical use case is as a drop-in replacement
    42   * for the <code>Hashtable</code> used in <code>LogFactory</code> for J2EE enviroments
    43   * running 1.3+ JVMs. Use of this class <i>in most cases</i> (see below) will
    44   * allow classloaders to be collected by the garbage collector without the need
    45   * to call {@link org.apache.commons.logging.LogFactory#release(ClassLoader) LogFactory.release(ClassLoader)}.
    46   * </p>
    47   *
    48   * <p><code>org.apache.commons.logging.LogFactory</code> checks whether this class
    49   * can be supported by the current JVM, and if so then uses it to store
    50   * references to the <code>LogFactory</code> implementationd it loads
    51   * (rather than using a standard Hashtable instance).
    52   * Having this class used instead of <code>Hashtable</code> solves
    53   * certain issues related to dynamic reloading of applications in J2EE-style
    54   * environments. However this class requires java 1.3 or later (due to its use
    55   * of <code>java.lang.ref.WeakReference</code> and associates).
    56   * And by the way, this extends <code>Hashtable</code> rather than <code>HashMap</code>
    57   * for backwards compatibility reasons. See the documentation
    58   * for method <code>LogFactory.createFactoryStore</code> for more details.</p>
    59   *
    60   * <p>The reason all this is necessary is due to a issue which
    61   * arises during hot deploy in a J2EE-like containers.
    62   * Each component running in the container owns one or more classloaders; when
    63   * the component loads a LogFactory instance via the component classloader
    64   * a reference to it gets stored in the static LogFactory.factories member,
    65   * keyed by the component's classloader so different components don't
    66   * stomp on each other. When the component is later unloaded, the container
    67   * sets the component's classloader to null with the intent that all the
    68   * component's classes get garbage-collected. However there's still a
    69   * reference to the component's classloader from a key in the "global"
    70   * <code>LogFactory</code>'s factories member! If <code>LogFactory.release()</code>
    71   * is called whenever component is unloaded, the classloaders will be correctly
    72   * garbage collected; this <i>should</i> be done by any container that
    73   * bundles commons-logging by default. However, holding the classloader
    74   * references weakly ensures that the classloader will be garbage collected
    75   * without the container performing this step. </p>
    76   *
    77   * <p>
    78   * <strong>Limitations:</strong>
    79   * There is still one (unusual) scenario in which a component will not
    80   * be correctly unloaded without an explicit release. Though weak references
    81   * are used for its keys, it is necessary to use strong references for its values.
    82   * </p>
    83   *
    84   * <p> If the abstract class <code>LogFactory</code> is
    85   * loaded by the container classloader but a subclass of
    86   * <code>LogFactory</code> [LogFactory1] is loaded by the component's
    87   * classloader and an instance stored in the static map associated with the
    88   * base LogFactory class, then there is a strong reference from the LogFactory
    89   * class to the LogFactory1 instance (as normal) and a strong reference from
    90   * the LogFactory1 instance to the component classloader via
    91   * <code>getClass().getClassLoader()</code>. This chain of references will prevent
    92   * collection of the child classloader.</p>
    93   *
    94   * <p>
    95   * Such a situation occurs when the commons-logging.jar is
    96   * loaded by a parent classloader (e.g. a server level classloader in a
    97   * servlet container) and a custom <code>LogFactory</code> implementation is
    98   * loaded by a child classloader (e.g. a web app classloader).</p>
    99   *
    100  * <p>To avoid this scenario, ensure
    101  * that any custom LogFactory subclass is loaded by the same classloader as
    102  * the base <code>LogFactory</code>. Creating custom LogFactory subclasses is,
    103  * however, rare. The standard LogFactoryImpl class should be sufficient
    104  * for most or all users.</p>
    105  *
    106  *
    107  * @author Brian Stansberry
    108  *
    109  * @since 1.1
    110  */
    111 public final class WeakHashtable extends Hashtable {
    112
    113     /**
    114      * The maximum number of times put() or remove() can be called before
    115      * the map will be purged of all cleared entries.
    116      */
    117     private static final int MAX_CHANGES_BEFORE_PURGE = 100;
    118
    119     /**
    120      * The maximum number of times put() or remove() can be called before
    121      * the map will be purged of one cleared entry.
    122      */
    123     private static final int PARTIAL_PURGE_COUNT     = 10;
    124
    125     /* ReferenceQueue we check for gc'd keys */
    126     private ReferenceQueue queue = new ReferenceQueue();
    127     /* Counter used to control how often we purge gc'd entries */
    128     private int changeCount = 0;
    129
    130     /**
    131      * Constructs a WeakHashtable with the Hashtable default
    132      * capacity and load factor.
    133      */
    134     public WeakHashtable() {}
    135
    136
    137     /**
    138      *@see Hashtable
    139      */
    140     public boolean containsKey(Object key) {
    141         // purge should not be required
    142         Referenced referenced = new Referenced(key);
    143         return super.containsKey(referenced);
    144     }
    145
    146     /**
    147      *@see Hashtable
    148      */
    149     public Enumeration elements() {
    150         purge();
    151         return super.elements();
    152     }
    153
    154     /**
    155      *@see Hashtable
    156      */
    157     public Set entrySet() {
    158         purge();
    159         Set referencedEntries = super.entrySet();
    160         Set unreferencedEntries = new HashSet();
    161         for (Iterator it=referencedEntries.iterator(); it.hasNext();) {
    162             Map.Entry entry = (Map.Entry) it.next();
    163             Referenced referencedKey = (Referenced) entry.getKey();
    164             Object key = referencedKey.getValue();
    165             Object value = entry.getValue();
    166             if (key != null) {
    167                 Entry dereferencedEntry = new Entry(key, value);
    168                 unreferencedEntries.add(dereferencedEntry);
    169             }
    170         }
    171         return unreferencedEntries;
    172     }
    173
    174     /**
    175      *@see Hashtable
    176      */
    177     public Object get(Object key) {
    178         // for performance reasons, no purge
    179         Referenced referenceKey = new Referenced(key);
    180         return super.get(referenceKey);
    181     }
    182
    183     /**
    184      *@see Hashtable
    185      */
    186     public Enumeration keys() {
    187         purge();
    188         final Enumeration enumer = super.keys();
    189         return new Enumeration() {
    190             public boolean hasMoreElements() {
    191                 return enumer.hasMoreElements();
    192             }
    193             public Object nextElement() {
    194                  Referenced nextReference = (Referenced) enumer.nextElement();
    195                  return nextReference.getValue();
    196             }
    197         };
    198     }
    199
    200
    201     /**
    202      *@see Hashtable
    203      */
    204     public Set keySet() {
    205         purge();
    206         Set referencedKeys = super.keySet();
    207         Set unreferencedKeys = new HashSet();
    208         for (Iterator it=referencedKeys.iterator(); it.hasNext();) {
    209             Referenced referenceKey = (Referenced) it.next();
    210             Object keyValue = referenceKey.getValue();
    211             if (keyValue != null) {
    212                 unreferencedKeys.add(keyValue);
    213             }
    214         }
    215         return unreferencedKeys;
    216     }
    217
    218     /**
    219      *@see Hashtable
    220      */
    221     public Object put(Object key, Object value) {
    222         // check for nulls, ensuring symantics match superclass
    223         if (key == null) {
    224             throw new NullPointerException("Null keys are not allowed");
    225         }
    226         if (value == null) {
    227             throw new NullPointerException("Null values are not allowed");
    228         }
    229
    230         // for performance reasons, only purge every
    231         // MAX_CHANGES_BEFORE_PURGE times
    232         if (changeCount++ > MAX_CHANGES_BEFORE_PURGE) {
    233             purge();
    234             changeCount = 0;
    235         }
    236         // do a partial purge more often
    237         else if ((changeCount % PARTIAL_PURGE_COUNT) == 0) {
    238             purgeOne();
    239         }
    240
    241         Referenced keyRef = new Referenced(key, queue);
    242         return super.put(keyRef, value);
    243     }
    244
    245     /**
    246      *@see Hashtable
    247      */
    248     public void putAll(Map t) {
    249         if (t != null) {
    250             Set entrySet = t.entrySet();
    251             for (Iterator it=entrySet.iterator(); it.hasNext();) {
    252                 Map.Entry entry = (Map.Entry) it.next();
    253                 put(entry.getKey(), entry.getValue());
    254             }
    255         }
    256     }
    257
    258     /**
    259      *@see Hashtable
    260      */
    261     public Collection values() {
    262         purge();
    263         return super.values();
    264     }
    265
    266     /**
    267      *@see Hashtable
    268      */
    269     public Object remove(Object key) {
    270         // for performance reasons, only purge every
    271         // MAX_CHANGES_BEFORE_PURGE times
    272         if (changeCount++ > MAX_CHANGES_BEFORE_PURGE) {
    273             purge();
    274             changeCount = 0;
    275         }
    276         // do a partial purge more often
    277         else if ((changeCount % PARTIAL_PURGE_COUNT) == 0) {
    278             purgeOne();
    279         }
    280         return super.remove(new Referenced(key));
    281     }
    282
    283     /**
    284      *@see Hashtable
    285      */
    286     public boolean isEmpty() {
    287         purge();
    288         return super.isEmpty();
    289     }
    290
    291     /**
    292      *@see Hashtable
    293      */
    294     public int size() {
    295         purge();
    296         return super.size();
    297     }
    298
    299     /**
    300      *@see Hashtable
    301      */
    302     public String toString() {
    303         purge();
    304         return super.toString();
    305     }
    306
    307     /**
    308      * @see Hashtable
    309      */
    310     protected void rehash() {
    311         // purge here to save the effort of rehashing dead entries
    312         purge();
    313         super.rehash();
    314     }
    315
    316     /**
    317      * Purges all entries whose wrapped keys
    318      * have been garbage collected.
    319      */
    320     private void purge() {
    321         synchronized (queue) {
    322             WeakKey key;
    323             while ((key = (WeakKey) queue.poll()) != null) {
    324                 super.remove(key.getReferenced());
    325             }
    326         }
    327     }
    328
    329     /**
    330      * Purges one entry whose wrapped key
    331      * has been garbage collected.
    332      */
    333     private void purgeOne() {
    334
    335         synchronized (queue) {
    336             WeakKey key = (WeakKey) queue.poll();
    337             if (key != null) {
    338                 super.remove(key.getReferenced());
    339             }
    340         }
    341     }
    342
    343     /** Entry implementation */
    344     private final static class Entry implements Map.Entry {
    345
    346         private final Object key;
    347         private final Object value;
    348
    349         private Entry(Object key, Object value) {
    350             this.key = key;
    351             this.value = value;
    352         }
    353
    354         public boolean equals(Object o) {
    355             boolean result = false;
    356             if (o != null && o instanceof Map.Entry) {
    357                 Map.Entry entry = (Map.Entry) o;
    358                 result =    (getKey()==null ?
    359                                             entry.getKey() == null :
    360                                             getKey().equals(entry.getKey()))
    361                             &&
    362                             (getValue()==null ?
    363                                             entry.getValue() == null :
    364                                             getValue().equals(entry.getValue()));
    365             }
    366             return result;
    367         }
    368
    369         public int hashCode() {
    370
    371             return (getKey()==null ? 0 : getKey().hashCode()) ^
    372                 (getValue()==null ? 0 : getValue().hashCode());
    373         }
    374
    375         public Object setValue(Object value) {
    376             throw new UnsupportedOperationException("Entry.setValue is not supported.");
    377         }
    378
    379         public Object getValue() {
    380             return value;
    381         }
    382
    383         public Object getKey() {
    384             return key;
    385         }
    386     }
    387
    388
    389     /** Wrapper giving correct symantics for equals and hashcode */
    390     private final static class Referenced {
    391
    392         private final WeakReference reference;
    393         private final int           hashCode;
    394
    395         /**
    396          *
    397          * @throws NullPointerException if referant is <code>null</code>
    398          */
    399         private Referenced(Object referant) {
    400             reference = new WeakReference(referant);
    401             // Calc a permanent hashCode so calls to Hashtable.remove()
    402             // work if the WeakReference has been cleared
    403             hashCode  = referant.hashCode();
    404         }
    405
    406         /**
    407          *
    408          * @throws NullPointerException if key is <code>null</code>
    409          */
    410         private Referenced(Object key, ReferenceQueue queue) {
    411             reference = new WeakKey(key, queue, this);
    412             // Calc a permanent hashCode so calls to Hashtable.remove()
    413             // work if the WeakReference has been cleared
    414             hashCode  = key.hashCode();
    415
    416         }
    417
    418         public int hashCode() {
    419             return hashCode;
    420         }
    421
    422         private Object getValue() {
    423             return reference.get();
    424         }
    425
    426         public boolean equals(Object o) {
    427             boolean result = false;
    428             if (o instanceof Referenced) {
    429                 Referenced otherKey = (Referenced) o;
    430                 Object thisKeyValue = getValue();
    431                 Object otherKeyValue = otherKey.getValue();
    432                 if (thisKeyValue == null) {
    433                     result = (otherKeyValue == null);
    434
    435                     // Since our hashcode was calculated from the original
    436                     // non-null referant, the above check breaks the
    437                     // hashcode/equals contract, as two cleared Referenced
    438                     // objects could test equal but have different hashcodes.
    439                     // We can reduce (not eliminate) the chance of this
    440                     // happening by comparing hashcodes.
    441                     if (result == true) {
    442                         result = (this.hashCode() == otherKey.hashCode());
    443                     }
    444                     // In any case, as our c'tor does not allow null referants
    445                     // and Hashtable does not do equality checks between
    446                     // existing keys, normal hashtable operations should never
    447                     // result in an equals comparison between null referants
    448                 }
    449                 else
    450                 {
    451                     result = thisKeyValue.equals(otherKeyValue);
    452                 }
    453             }
    454             return result;
    455         }
    456     }
    457
    458     /**
    459      * WeakReference subclass that holds a hard reference to an
    460      * associated <code>value</code> and also makes accessible
    461      * the Referenced object holding it.
    462      */
    463     private final static class WeakKey extends WeakReference {
    464
    465         private final Referenced referenced;
    466
    467         private WeakKey(Object key,
    468                         ReferenceQueue queue,
    469                         Referenced referenced) {
    470             super(key, queue);
    471             this.referenced = referenced;
    472         }
    473
    474         private Referenced getReferenced() {
    475             return referenced;
    476         }
    477      }
    478 }