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