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