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