1 /* 2 * Copyright (c) 1998, 2006, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.misc; 27 28 import java.lang.ref.SoftReference; 29 import java.lang.ref.ReferenceQueue; 30 31 import java.util.Iterator; 32 import java.util.Map; 33 import java.util.AbstractMap; 34 import java.util.HashMap; 35 import java.util.Set; 36 import java.util.AbstractSet; 37 import java.util.NoSuchElementException; 38 39 40 /** 41 * A memory-sensitive implementation of the <code>Map</code> interface. 42 * 43 * <p> A <code>SoftCache</code> object uses {@link java.lang.ref.SoftReference 44 * soft references} to implement a memory-sensitive hash map. If the garbage 45 * collector determines at a certain point in time that a value object in a 46 * <code>SoftCache</code> entry is no longer strongly reachable, then it may 47 * remove that entry in order to release the memory occupied by the value 48 * object. All <code>SoftCache</code> objects are guaranteed to be completely 49 * cleared before the virtual machine will throw an 50 * <code>OutOfMemoryError</code>. Because of this automatic clearing feature, 51 * the behavior of this class is somewhat different from that of other 52 * <code>Map</code> implementations. 53 * 54 * <p> Both null values and the null key are supported. This class has the 55 * same performance characteristics as the <code>HashMap</code> class, and has 56 * the same efficiency parameters of <em>initial capacity</em> and <em>load 57 * factor</em>. 58 * 59 * <p> Like most collection classes, this class is not synchronized. A 60 * synchronized <code>SoftCache</code> may be constructed using the 61 * <code>Collections.synchronizedMap</code> method. 62 * 63 * <p> In typical usage this class will be subclassed and the <code>fill</code> 64 * method will be overridden. When the <code>get</code> method is invoked on a 65 * key for which there is no mapping in the cache, it will in turn invoke the 66 * <code>fill</code> method on that key in an attempt to construct a 67 * corresponding value. If the <code>fill</code> method returns such a value 68 * then the cache will be updated and the new value will be returned. Thus, 69 * for example, a simple URL-content cache can be constructed as follows: 70 * 71 * <pre> 72 * public class URLCache extends SoftCache { 73 * protected Object fill(Object key) { 74 * return ((URL)key).getContent(); 75 * } 76 * } 77 * </pre> 78 * 79 * <p> The behavior of the <code>SoftCache</code> class depends in part upon 80 * the actions of the garbage collector, so several familiar (though not 81 * required) <code>Map</code> invariants do not hold for this class. <p> 82 * Because entries are removed from a <code>SoftCache</code> in response to 83 * dynamic advice from the garbage collector, a <code>SoftCache</code> may 84 * behave as though an unknown thread is silently removing entries. In 85 * particular, even if you synchronize on a <code>SoftCache</code> instance and 86 * invoke none of its mutator methods, it is possible for the <code>size</code> 87 * method to return smaller values over time, for the <code>isEmpty</code> 88 * method to return <code>false</code> and then <code>true</code>, for the 89 * <code>containsKey</code> method to return <code>true</code> and later 90 * <code>false</code> for a given key, for the <code>get</code> method to 91 * return a value for a given key but later return <code>null</code>, for the 92 * <code>put</code> method to return <code>null</code> and the 93 * <code>remove</code> method to return <code>false</code> for a key that 94 * previously appeared to be in the map, and for successive examinations of the 95 * key set, the value set, and the entry set to yield successively smaller 96 * numbers of elements. 97 * 98 * @author Mark Reinhold 99 * @since 1.2 100 * @see java.util.HashMap 101 * @see java.lang.ref.SoftReference 102 */ 103 104 105 public class SoftCache extends AbstractMap implements Map { 106 107 /* The basic idea of this implementation is to maintain an internal HashMap 108 that maps keys to soft references whose referents are the keys' values; 109 the various accessor methods dereference these soft references before 110 returning values. Because we don't have access to the innards of the 111 HashMap, each soft reference must contain the key that maps to it so 112 that the processQueue method can remove keys whose values have been 113 discarded. Thus the HashMap actually maps keys to instances of the 114 ValueCell class, which is a simple extension of the SoftReference class. 115 */ 116 117 118 static private class ValueCell extends SoftReference { 119 static private Object INVALID_KEY = new Object(); 120 static private int dropped = 0; 121 private Object key; 122 ValueCell(Object key, Object value, ReferenceQueue queue)123 private ValueCell(Object key, Object value, ReferenceQueue queue) { 124 super(value, queue); 125 this.key = key; 126 } 127 create(Object key, Object value, ReferenceQueue queue)128 private static ValueCell create(Object key, Object value, 129 ReferenceQueue queue) 130 { 131 if (value == null) return null; 132 return new ValueCell(key, value, queue); 133 } 134 strip(Object val, boolean drop)135 private static Object strip(Object val, boolean drop) { 136 if (val == null) return null; 137 ValueCell vc = (ValueCell)val; 138 Object o = vc.get(); 139 if (drop) vc.drop(); 140 return o; 141 } 142 isValid()143 private boolean isValid() { 144 return (key != INVALID_KEY); 145 } 146 drop()147 private void drop() { 148 super.clear(); 149 key = INVALID_KEY; 150 dropped++; 151 } 152 153 } 154 155 156 /* Hash table mapping keys to ValueCells */ 157 private Map hash; 158 159 /* Reference queue for cleared ValueCells */ 160 private ReferenceQueue queue = new ReferenceQueue(); 161 162 163 /* Process any ValueCells that have been cleared and enqueued by the 164 garbage collector. This method should be invoked once by each public 165 mutator in this class. We don't invoke this method in public accessors 166 because that can lead to surprising ConcurrentModificationExceptions. 167 */ processQueue()168 private void processQueue() { 169 ValueCell vc; 170 while ((vc = (ValueCell)queue.poll()) != null) { 171 if (vc.isValid()) hash.remove(vc.key); 172 else ValueCell.dropped--; 173 } 174 } 175 176 177 /* -- Constructors -- */ 178 179 /** 180 * Construct a new, empty <code>SoftCache</code> with the given 181 * initial capacity and the given load factor. 182 * 183 * @param initialCapacity The initial capacity of the cache 184 * 185 * @param loadFactor A number between 0.0 and 1.0 186 * 187 * @throws IllegalArgumentException If the initial capacity is less than 188 * or equal to zero, or if the load 189 * factor is less than zero 190 */ SoftCache(int initialCapacity, float loadFactor)191 public SoftCache(int initialCapacity, float loadFactor) { 192 hash = new HashMap(initialCapacity, loadFactor); 193 } 194 195 /** 196 * Construct a new, empty <code>SoftCache</code> with the given 197 * initial capacity and the default load factor. 198 * 199 * @param initialCapacity The initial capacity of the cache 200 * 201 * @throws IllegalArgumentException If the initial capacity is less than 202 * or equal to zero 203 */ SoftCache(int initialCapacity)204 public SoftCache(int initialCapacity) { 205 hash = new HashMap(initialCapacity); 206 } 207 208 /** 209 * Construct a new, empty <code>SoftCache</code> with the default 210 * capacity and the default load factor. 211 */ SoftCache()212 public SoftCache() { 213 hash = new HashMap(); 214 } 215 216 217 /* -- Simple queries -- */ 218 219 /** 220 * Return the number of key-value mappings in this cache. The time 221 * required by this operation is linear in the size of the map. 222 */ size()223 public int size() { 224 return entrySet().size(); 225 } 226 227 /** 228 * Return <code>true</code> if this cache contains no key-value mappings. 229 */ isEmpty()230 public boolean isEmpty() { 231 return entrySet().isEmpty(); 232 } 233 234 /** 235 * Return <code>true</code> if this cache contains a mapping for the 236 * specified key. If there is no mapping for the key, this method will not 237 * attempt to construct one by invoking the <code>fill</code> method. 238 * 239 * @param key The key whose presence in the cache is to be tested 240 */ containsKey(Object key)241 public boolean containsKey(Object key) { 242 return ValueCell.strip(hash.get(key), false) != null; 243 } 244 245 246 /* -- Lookup and modification operations -- */ 247 248 /** 249 * Create a value object for the given <code>key</code>. This method is 250 * invoked by the <code>get</code> method when there is no entry for 251 * <code>key</code>. If this method returns a non-<code>null</code> value, 252 * then the cache will be updated to map <code>key</code> to that value, 253 * and that value will be returned by the <code>get</code> method. 254 * 255 * <p> The default implementation of this method simply returns 256 * <code>null</code> for every <code>key</code> value. A subclass may 257 * override this method to provide more useful behavior. 258 * 259 * @param key The key for which a value is to be computed 260 * 261 * @return A value for <code>key</code>, or <code>null</code> if one 262 * could not be computed 263 * @see #get 264 */ fill(Object key)265 protected Object fill(Object key) { 266 return null; 267 } 268 269 /** 270 * Return the value to which this cache maps the specified 271 * <code>key</code>. If the cache does not presently contain a value for 272 * this key, then invoke the <code>fill</code> method in an attempt to 273 * compute such a value. If that method returns a non-<code>null</code> 274 * value, then update the cache and return the new value. Otherwise, 275 * return <code>null</code>. 276 * 277 * <p> Note that because this method may update the cache, it is considered 278 * a mutator and may cause <code>ConcurrentModificationException</code>s to 279 * be thrown if invoked while an iterator is in use. 280 * 281 * @param key The key whose associated value, if any, is to be returned 282 * 283 * @see #fill 284 */ get(Object key)285 public Object get(Object key) { 286 processQueue(); 287 Object v = hash.get(key); 288 if (v == null) { 289 v = fill(key); 290 if (v != null) { 291 hash.put(key, ValueCell.create(key, v, queue)); 292 return v; 293 } 294 } 295 return ValueCell.strip(v, false); 296 } 297 298 /** 299 * Update this cache so that the given <code>key</code> maps to the given 300 * <code>value</code>. If the cache previously contained a mapping for 301 * <code>key</code> then that mapping is replaced and the old value is 302 * returned. 303 * 304 * @param key The key that is to be mapped to the given 305 * <code>value</code> 306 * @param value The value to which the given <code>key</code> is to be 307 * mapped 308 * 309 * @return The previous value to which this key was mapped, or 310 * <code>null</code> if if there was no mapping for the key 311 */ put(Object key, Object value)312 public Object put(Object key, Object value) { 313 processQueue(); 314 ValueCell vc = ValueCell.create(key, value, queue); 315 return ValueCell.strip(hash.put(key, vc), true); 316 } 317 318 /** 319 * Remove the mapping for the given <code>key</code> from this cache, if 320 * present. 321 * 322 * @param key The key whose mapping is to be removed 323 * 324 * @return The value to which this key was mapped, or <code>null</code> if 325 * there was no mapping for the key 326 */ remove(Object key)327 public Object remove(Object key) { 328 processQueue(); 329 return ValueCell.strip(hash.remove(key), true); 330 } 331 332 /** 333 * Remove all mappings from this cache. 334 */ clear()335 public void clear() { 336 processQueue(); 337 hash.clear(); 338 } 339 340 341 /* -- Views -- */ 342 valEquals(Object o1, Object o2)343 private static boolean valEquals(Object o1, Object o2) { 344 return (o1 == null) ? (o2 == null) : o1.equals(o2); 345 } 346 347 348 /* Internal class for entries. 349 Because it uses SoftCache.this.queue, this class cannot be static. 350 */ 351 private class Entry implements Map.Entry { 352 private Map.Entry ent; 353 private Object value; /* Strong reference to value, to prevent the GC 354 from flushing the value while this Entry 355 exists */ 356 Entry(Map.Entry ent, Object value)357 Entry(Map.Entry ent, Object value) { 358 this.ent = ent; 359 this.value = value; 360 } 361 getKey()362 public Object getKey() { 363 return ent.getKey(); 364 } 365 getValue()366 public Object getValue() { 367 return value; 368 } 369 setValue(Object value)370 public Object setValue(Object value) { 371 return ent.setValue(ValueCell.create(ent.getKey(), value, queue)); 372 } 373 equals(Object o)374 public boolean equals(Object o) { 375 if (! (o instanceof Map.Entry)) return false; 376 Map.Entry e = (Map.Entry)o; 377 return (valEquals(ent.getKey(), e.getKey()) 378 && valEquals(value, e.getValue())); 379 } 380 hashCode()381 public int hashCode() { 382 Object k; 383 return ((((k = getKey()) == null) ? 0 : k.hashCode()) 384 ^ ((value == null) ? 0 : value.hashCode())); 385 } 386 387 } 388 389 390 /* Internal class for entry sets */ 391 private class EntrySet extends AbstractSet { 392 Set hashEntries = hash.entrySet(); 393 iterator()394 public Iterator iterator() { 395 396 return new Iterator() { 397 Iterator hashIterator = hashEntries.iterator(); 398 Entry next = null; 399 400 public boolean hasNext() { 401 while (hashIterator.hasNext()) { 402 Map.Entry ent = (Map.Entry)hashIterator.next(); 403 ValueCell vc = (ValueCell)ent.getValue(); 404 Object v = null; 405 if ((vc != null) && ((v = vc.get()) == null)) { 406 /* Value has been flushed by GC */ 407 continue; 408 } 409 next = new Entry(ent, v); 410 return true; 411 } 412 return false; 413 } 414 415 public Object next() { 416 if ((next == null) && !hasNext()) 417 throw new NoSuchElementException(); 418 Entry e = next; 419 next = null; 420 return e; 421 } 422 423 public void remove() { 424 hashIterator.remove(); 425 } 426 427 }; 428 } 429 isEmpty()430 public boolean isEmpty() { 431 return !(iterator().hasNext()); 432 } 433 size()434 public int size() { 435 int j = 0; 436 for (Iterator i = iterator(); i.hasNext(); i.next()) j++; 437 return j; 438 } 439 remove(Object o)440 public boolean remove(Object o) { 441 processQueue(); 442 if (o instanceof Entry) return hashEntries.remove(((Entry)o).ent); 443 else return false; 444 } 445 446 } 447 448 449 private Set entrySet = null; 450 451 /** 452 * Return a <code>Set</code> view of the mappings in this cache. 453 */ entrySet()454 public Set entrySet() { 455 if (entrySet == null) entrySet = new EntrySet(); 456 return entrySet; 457 } 458 459 } 460