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 * http://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 ? 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}