001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.logging.impl; 019 020import java.lang.ref.ReferenceQueue; 021import java.lang.ref.WeakReference; 022import java.util.ArrayList; 023import java.util.Collection; 024import java.util.Enumeration; 025import java.util.HashSet; 026import java.util.Hashtable; 027import java.util.List; 028import java.util.Map; 029import java.util.Objects; 030import java.util.Set; 031 032/** 033 * Implements {@code Hashtable} to use {@code WeakReference}'s 034 * to hold its keys thus allowing them to be reclaimed by the garbage collector. 035 * The associated values are retained using strong references. 036 * <p> 037 * This class follows the semantics of {@code Hashtable} as closely as 038 * possible. It therefore does not accept null values or keys. 039 * </p> 040 * <p> 041 * <strong>Note:</strong> 042 * This is <em>not</em> intended to be a general purpose hash table replacement. 043 * This implementation is also tuned towards a particular purpose: for use as a replacement 044 * for {@code Hashtable} in {@code LogFactory}. This application requires 045 * good liveliness for {@code get} and {@code put}. Various tradeoffs 046 * have been made with this in mind. 047 * </p> 048 * <p> 049 * <strong>Usage:</strong> typical use case is as a drop-in replacement 050 * for the {@code Hashtable} used in {@code LogFactory} for J2EE environments 051 * running 1.3+ JVMs. Use of this class <em>in most cases</em> (see below) will 052 * allow class loaders to be collected by the garbage collector without the need 053 * to call {@link org.apache.commons.logging.LogFactory#release(ClassLoader) LogFactory.release(ClassLoader)}. 054 * </p> 055 * <p> 056 * {@code org.apache.commons.logging.LogFactory} checks whether this class 057 * can be supported by the current JVM, and if so then uses it to store 058 * references to the {@code LogFactory} implementation it loads 059 * (rather than using a standard Hashtable instance). 060 * Having this class used instead of {@code Hashtable} solves 061 * certain issues related to dynamic reloading of applications in J2EE-style 062 * environments. However this class requires Java 1.3 or later (due to its use 063 * of {@link java.lang.ref.WeakReference} and associates). 064 * And by the way, this extends {@code Hashtable} rather than {@code HashMap} 065 * for backwards compatibility reasons. See the documentation 066 * for method {@code LogFactory.createFactoryStore} for more details. 067 * </p> 068 * <p> 069 * The reason all this is necessary is due to a issue which 070 * arises during hot deploy in a J2EE-like containers. 071 * Each component running in the container owns one or more class loaders; when 072 * the component loads a LogFactory instance via the component class loader 073 * a reference to it gets stored in the static LogFactory.factories member, 074 * keyed by the component's class loader so different components don't 075 * stomp on each other. When the component is later unloaded, the container 076 * sets the component's class loader to null with the intent that all the 077 * component's classes get garbage-collected. However there's still a 078 * reference to the component's class loader from a key in the "global" 079 * {@code LogFactory}'s factories member! If {@code LogFactory.release()} 080 * is called whenever component is unloaded, the class loaders will be correctly 081 * garbage collected; this <em>should</em> be done by any container that 082 * bundles commons-logging by default. However, holding the class loader 083 * references weakly ensures that the class loader will be garbage collected 084 * without the container performing this step. 085 * </p> 086 * <p> 087 * <strong>Limitations:</strong> 088 * There is still one (unusual) scenario in which a component will not 089 * be correctly unloaded without an explicit release. Though weak references 090 * are used for its keys, it is necessary to use strong references for its values. 091 * </p> 092 * <p> 093 * If the abstract class {@code LogFactory} is 094 * loaded by the container class loader but a subclass of 095 * {@code LogFactory} [LogFactory1] is loaded by the component's 096 * class loader and an instance stored in the static map associated with the 097 * base LogFactory class, then there is a strong reference from the LogFactory 098 * class to the LogFactory1 instance (as normal) and a strong reference from 099 * 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 121public 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}