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    *      https://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 ? entry.getKey() == null : getKey().equals(entry.getKey()))
140                         && (getValue() == null ? entry.getValue() == null : getValue().equals(entry.getValue()));
141             }
142             return result;
143         }
144 
145         @Override
146         public Object getKey() {
147             return key;
148         }
149 
150         @Override
151         public Object getValue() {
152             return value;
153         }
154 
155         @Override
156         public int hashCode() {
157             return (getKey() == null ? 0 : getKey().hashCode()) ^ (getValue() == null ? 0 : getValue().hashCode());
158         }
159 
160         @Override
161         public Object setValue(final Object value) {
162             throw new UnsupportedOperationException("Entry.setValue is not supported.");
163         }
164     }
165 
166     /** Wrapper giving correct symantics for equals and hash code */
167     private final static class Referenced {
168 
169         private final WeakReference reference;
170         private final int hashCode;
171 
172         /**
173          *
174          * @throws NullPointerException if referant is {@code null}
175          */
176         private Referenced(final Object referant) {
177             reference = new WeakReference(referant);
178             // Calc a permanent hashCode so calls to Hashtable.remove()
179             // work if the WeakReference has been cleared
180             hashCode = referant.hashCode();
181         }
182 
183         /**
184          *
185          * @throws NullPointerException if key is {@code null}
186          */
187         private Referenced(final Object key, final ReferenceQueue queue) {
188             reference = new WeakKey(key, queue, this);
189             // Calc a permanent hashCode so calls to Hashtable.remove()
190             // work if the WeakReference has been cleared
191             hashCode = key.hashCode();
192         }
193 
194         @Override
195         public boolean equals(final Object o) {
196             boolean result = false;
197             if (o instanceof Referenced) {
198                 final Referenced otherKey = (Referenced) o;
199                 final Object thisKeyValue = getValue();
200                 final Object otherKeyValue = otherKey.getValue();
201                 if (thisKeyValue == null) {
202                     result = otherKeyValue == null;
203                     // Since our hash code was calculated from the original
204                     // non-null referant, the above check breaks the
205                     // hash code/equals contract, as two cleared Referenced
206                     // objects could test equal but have different hash codes.
207                     // We can reduce (not eliminate) the chance of this
208                     // happening by comparing hash codes.
209                     result = result && hashCode() == otherKey.hashCode();
210                     // In any case, as our constructor does not allow null referants
211                     // and Hashtable does not do equality checks between
212                     // existing keys, normal hash table operations should never
213                     // result in an equals comparison between null referants
214                 } else {
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, final ReferenceQueue queue, final Referenced referenced) {
241             super(key, queue);
242             this.referenced = referenced;
243         }
244 
245         private Referenced getReferenced() {
246             return referenced;
247         }
248     }
249 
250     /** Serializable version identifier. */
251     private static final long serialVersionUID = -1546036869799732453L;
252 
253     /**
254      * The maximum number of times put() or remove() can be called before
255      * the map will be purged of all cleared entries.
256      */
257     private static final int MAX_CHANGES_BEFORE_PURGE = 100;
258 
259     /**
260      * The maximum number of times put() or remove() can be called before
261      * the map will be purged of one cleared entry.
262      */
263     private static final int PARTIAL_PURGE_COUNT = 10;
264 
265     /** ReferenceQueue we check for GC'd keys. */
266     private final transient ReferenceQueue queue = new ReferenceQueue();
267 
268     /** Counter used to control how often we purge gc'd entries. */
269     private int changeCount;
270 
271     /**
272      * Constructs a WeakHashtable with the Hashtable default
273      * capacity and load factor.
274      */
275     public WeakHashtable() {
276         // empty
277     }
278 
279     /**
280      *@see Hashtable
281      */
282     @Override
283     public boolean containsKey(final Object key) {
284         // purge should not be required
285         return super.containsKey(new Referenced(key));
286     }
287 
288     /**
289      *@see Hashtable
290      */
291     @Override
292     public Enumeration elements() {
293         purge();
294         return super.elements();
295     }
296 
297     /**
298      *@see Hashtable
299      */
300     @Override
301     public Set entrySet() {
302         purge();
303         final Set referencedEntries = super.entrySet();
304         final Set unreferencedEntries = new HashSet();
305         for (final Object referencedEntry : referencedEntries) {
306             final Map.Entry entry = (Map.Entry) referencedEntry;
307             final Referenced referencedKey = (Referenced) entry.getKey();
308             final Object key = referencedKey.getValue();
309             final Object value = entry.getValue();
310             if (key != null) {
311                 final Entry dereferencedEntry = new Entry(key, value);
312                 unreferencedEntries.add(dereferencedEntry);
313             }
314         }
315         return unreferencedEntries;
316     }
317 
318     /**
319      *@see Hashtable
320      */
321     @Override
322     public Object get(final Object key) {
323         // for performance reasons, no purge
324         return super.get(new Referenced(key));
325     }
326 
327     /**
328      *@see Hashtable
329      */
330     @Override
331     public boolean isEmpty() {
332         purge();
333         return super.isEmpty();
334     }
335 
336     /**
337      *@see Hashtable
338      */
339     @Override
340     public Enumeration keys() {
341         purge();
342         final Enumeration enumer = super.keys();
343         return new Enumeration() {
344             @Override
345             public boolean hasMoreElements() {
346                 return enumer.hasMoreElements();
347             }
348             @Override
349             public Object nextElement() {
350                  final Referenced nextReference = (Referenced) enumer.nextElement();
351                  return nextReference.getValue();
352             }
353         };
354     }
355 
356     /**
357      *@see Hashtable
358      */
359     @Override
360     public Set keySet() {
361         purge();
362         final Set referencedKeys = super.keySet();
363         final Set unreferencedKeys = new HashSet();
364         for (final Object referencedKey : referencedKeys) {
365             final Referenced referenceKey = (Referenced) referencedKey;
366             final Object keyValue = referenceKey.getValue();
367             if (keyValue != null) {
368                 unreferencedKeys.add(keyValue);
369             }
370         }
371         return unreferencedKeys;
372     }
373 
374     /**
375      * Purges all entries whose wrapped keys
376      * have been garbage collected.
377      */
378     private void purge() {
379         final List toRemove = new ArrayList<>();
380         synchronized (queue) {
381             WeakKey key;
382             while ((key = (WeakKey) queue.poll()) != null) {
383                 toRemove.add(key.getReferenced());
384             }
385         }
386         // LOGGING-119: do the actual removal of the keys outside the sync block
387         // to prevent deadlock scenarios as purge() may be called from
388         // non-synchronized methods too
389         final int size = toRemove.size();
390         for (int i = 0; i < size; i++) {
391             super.remove(toRemove.get(i));
392         }
393     }
394 
395     /**
396      * Purges one entry whose wrapped key
397      * has been garbage collected.
398      */
399     private void purgeOne() {
400         synchronized (queue) {
401             final WeakKey key = (WeakKey) queue.poll();
402             if (key != null) {
403                 super.remove(key.getReferenced());
404             }
405         }
406     }
407 
408     /**
409      *@see Hashtable
410      */
411     @Override
412     public synchronized Object put(final Object key, final Object value) {
413         // check for nulls, ensuring semantics match superclass
414         Objects.requireNonNull(key, "key");
415         Objects.requireNonNull(value, "value");
416         // for performance reasons, only purge every
417         // MAX_CHANGES_BEFORE_PURGE times
418         if (changeCount++ > MAX_CHANGES_BEFORE_PURGE) {
419             purge();
420             changeCount = 0;
421         }
422         // do a partial purge more often
423         else if (changeCount % PARTIAL_PURGE_COUNT == 0) {
424             purgeOne();
425         }
426         final Referenced keyRef = new Referenced(key, queue);
427         return super.put(keyRef, value);
428     }
429 
430     /**
431      *@see Hashtable
432      */
433     @Override
434     public void putAll(final Map t) {
435         if (t != null) {
436             final Set entrySet = t.entrySet();
437             for (final Object element : entrySet) {
438                 final Map.Entry entry = (Map.Entry) element;
439                 put(entry.getKey(), entry.getValue());
440             }
441         }
442     }
443 
444     /**
445      * @see Hashtable
446      */
447     @Override
448     protected void rehash() {
449         // purge here to save the effort of rehashing dead entries
450         purge();
451         super.rehash();
452     }
453 
454     /**
455      *@see Hashtable
456      */
457     @Override
458     public synchronized Object remove(final Object key) {
459         // for performance reasons, only purge every
460         // MAX_CHANGES_BEFORE_PURGE times
461         if (changeCount++ > MAX_CHANGES_BEFORE_PURGE) {
462             purge();
463             changeCount = 0;
464         }
465         // do a partial purge more often
466         else if (changeCount % PARTIAL_PURGE_COUNT == 0) {
467             purgeOne();
468         }
469         return super.remove(new Referenced(key));
470     }
471 
472     /**
473      *@see Hashtable
474      */
475     @Override
476     public int size() {
477         purge();
478         return super.size();
479     }
480 
481     /**
482      *@see Hashtable
483      */
484     @Override
485     public String toString() {
486         purge();
487         return super.toString();
488     }
489 
490     /**
491      *@see Hashtable
492      */
493     @Override
494     public Collection values() {
495         purge();
496         return super.values();
497     }
498 }