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 }