1 /* 2 * Copyright (c) 2023, 2024, 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 jdk.internal.util; 27 28 import java.lang.ref.Reference; 29 import java.lang.ref.ReferenceQueue; 30 import java.lang.ref.SoftReference; 31 import java.lang.ref.WeakReference; 32 import java.util.AbstractMap; 33 import java.util.Collection; 34 import java.util.HashMap; 35 import java.util.Objects; 36 import java.util.Map; 37 import java.util.Set; 38 import java.util.concurrent.ConcurrentHashMap; 39 import java.util.function.Supplier; 40 import java.util.function.UnaryOperator; 41 import java.util.stream.Collectors; 42 import java.util.stream.Stream; 43 44 import jdk.internal.access.SharedSecrets; 45 46 /** 47 * This class provides management of {@link Map maps} where it is desirable to 48 * remove entries automatically when the key is garbage collected. This is 49 * accomplished by using a backing map where the keys are either a 50 * {@link WeakReference} or a {@link SoftReference}. 51 * Keys must be {@linkplain Class#isIdentity() identity objects.} 52 * <p> 53 * To create a {@link ReferencedKeyMap} the user must provide a {@link Supplier} 54 * of the backing map and whether {@link WeakReference} or 55 * {@link SoftReference} is to be used. 56 * 57 * {@snippet : 58 * // Use HashMap and WeakReference 59 * Map<Long, String> map = ReferencedKeyMap.create(false, HashMap::new); 60 * map.put(10_000_000L, "a"); 61 * map.put(10_000_001L, "b"); 62 * map.put(10_000_002L, "c"); 63 * map.put(10_000_003L, "d"); 64 * map.put(10_000_004L, "e"); 65 * 66 * // Use ConcurrentHashMap and SoftReference 67 * map = ReferencedKeyMap.create(true, ConcurrentHashMap::new); 68 * map.put(20_000_000L, "v"); 69 * map.put(20_000_001L, "w"); 70 * map.put(20_000_002L, "x"); 71 * map.put(20_000_003L, "y"); 72 * map.put(20_000_004L, "z"); 73 * } 74 * 75 * @implNote Care must be given that the backing map does replacement by 76 * replacing the value in the map entry instead of deleting the old entry and 77 * adding a new entry, otherwise replaced entries may end up with a strongly 78 * referenced key. {@link HashMap} and {@link ConcurrentHashMap} are known 79 * to be safe. 80 * 81 * @param <K> the type of keys maintained by this map 82 * @param <V> the type of mapped values 83 * 84 * @since 21 85 */ 86 public final class ReferencedKeyMap<K, V> implements Map<K, V> { 87 /** 88 * true if {@link SoftReference} keys are to be used, 89 * {@link WeakReference} otherwise. 90 */ 91 private final boolean isSoft; 92 93 /** 94 * Backing {@link Map}. 95 */ 96 private final Map<ReferenceKey<K>, V> map; 97 98 /** 99 * {@link ReferenceQueue} for cleaning up entries. 100 */ 101 private final ReferenceQueue<K> stale; 102 103 /** 104 * @return a supplier to create a {@code ConcurrentHashMap} appropriate for use in the 105 * create methods. 106 * @param <K> the type of keys maintained by the new map 107 * @param <V> the type of mapped values 108 */ 109 public static <K, V> Supplier<Map<ReferenceKey<K>, V>> concurrentHashMapSupplier() { 110 return new Supplier<>() { 111 @Override 112 public Map<ReferenceKey<K>, V> get() { 113 return new ConcurrentHashMap<>(); 114 } 115 }; 116 } 117 118 /** 119 * Private constructor. 120 * 121 * @param isSoft true if {@link SoftReference} keys are to 122 * be used, {@link WeakReference} otherwise. 123 * @param map backing map 124 * @param stale {@link ReferenceQueue} for cleaning up entries 125 */ 126 private ReferencedKeyMap(boolean isSoft, Map<ReferenceKey<K>, V> map, ReferenceQueue<K> stale) { 127 this.isSoft = isSoft; 128 this.map = map; 129 this.stale = stale; 130 } 131 132 /** 133 * Create a new {@link ReferencedKeyMap} map. 134 * 135 * @param isSoft true if {@link SoftReference} keys are to 136 * be used, {@link WeakReference} otherwise. 137 * @param supplier {@link Supplier} of the backing map 138 * 139 * @return a new map with {@link Reference} keys 140 * 141 * @param <K> the type of keys maintained by the new map 142 * @param <V> the type of mapped values 143 */ 144 public static <K, V> ReferencedKeyMap<K, V> 145 create(boolean isSoft, Supplier<Map<ReferenceKey<K>, V>> supplier) { 146 return create(isSoft, false, supplier); 147 } 148 149 /** 150 * Create a new {@link ReferencedKeyMap} map. 151 * 152 * @param isSoft true if {@link SoftReference} keys are to 153 * be used, {@link WeakReference} otherwise. 154 * @param useNativeQueue true if uses NativeReferenceQueue 155 * otherwise use {@link ReferenceQueue}. 156 * @param supplier {@link Supplier} of the backing map 157 * 158 * @return a new map with {@link Reference} keys 159 * 160 * @param <K> the type of keys maintained by the new map 161 * @param <V> the type of mapped values 162 */ 163 public static <K, V> ReferencedKeyMap<K, V> 164 create(boolean isSoft, boolean useNativeQueue, Supplier<Map<ReferenceKey<K>, V>> supplier) { 165 return new ReferencedKeyMap<K, V>(isSoft, supplier.get(), 166 useNativeQueue ? SharedSecrets.getJavaLangRefAccess().newNativeReferenceQueue() 167 : new ReferenceQueue<>() 168 ); 169 } 170 171 /** 172 * {@return a key suitable for a map entry} 173 * 174 * @param key unwrapped key 175 */ 176 @SuppressWarnings("unchecked") 177 private ReferenceKey<K> entryKey(Object key) { 178 if (isSoft) { 179 return new SoftReferenceKey<>((K)key, stale); 180 } else { 181 return new WeakReferenceKey<>((K)key, stale); 182 } 183 } 184 185 /** 186 * {@return a key suitable for lookup} 187 * 188 * @param key unwrapped key 189 */ 190 @SuppressWarnings("unchecked") 191 private ReferenceKey<K> lookupKey(Object key) { 192 return new StrongReferenceKey<>((K)key); 193 } 194 195 @Override 196 public int size() { 197 removeStaleReferences(); 198 return map.size(); 199 } 200 201 @Override 202 public boolean isEmpty() { 203 removeStaleReferences(); 204 return map.isEmpty(); 205 } 206 207 @Override 208 public boolean containsKey(Object key) { 209 Objects.requireNonNull(key, "key must not be null"); 210 removeStaleReferences(); 211 return map.containsKey(lookupKey(key)); 212 } 213 214 @Override 215 public boolean containsValue(Object value) { 216 Objects.requireNonNull(value, "value must not be null"); 217 removeStaleReferences(); 218 return map.containsValue(value); 219 } 220 221 @Override 222 public V get(Object key) { 223 removeStaleReferences(); 224 return getNoCheckStale(key); 225 } 226 227 // Internal get(key) without removing stale references that would modify the keyset. 228 // Use when iterating or streaming over the keys to avoid ConcurrentModificationException. 229 private V getNoCheckStale(Object key) { 230 Objects.requireNonNull(key, "key must not be null"); 231 return map.get(lookupKey(key)); 232 } 233 234 @Override 235 public V put(K key, V newValue) { 236 Objects.requireNonNull(key, "key must not be null"); 237 Objects.requireNonNull(newValue, "value must not be null"); 238 removeStaleReferences(); 239 ReferenceKey<K> entryKey = entryKey(key); 240 // If {@code put} returns non-null then was actually a {@code replace} 241 // and older key was used. In that case the new key was not used and the 242 // reference marked stale. 243 V oldValue = map.put(entryKey, newValue); 244 if (oldValue != null) { 245 entryKey.unused(); 246 } 247 return oldValue; 248 } 249 250 @Override 251 public V remove(Object key) { 252 // Rely on gc to clean up old key. 253 return map.remove(lookupKey(key)); 254 } 255 256 @Override 257 public void putAll(Map<? extends K, ? extends V> m) { 258 removeStaleReferences(); 259 for (Entry<? extends K, ? extends V> entry : m.entrySet()) { 260 K key = entry.getKey(); 261 V value = entry.getValue(); 262 put(key, value); 263 } 264 } 265 266 @Override 267 public void clear() { 268 removeStaleReferences(); 269 // Rely on gc to clean up old keys. 270 map.clear(); 271 } 272 273 /** 274 * Common routine for collecting the current set of keys. 275 * 276 * @return {@link Stream} of valid keys (unwrapped) 277 */ 278 private Stream<K> filterKeySet() { 279 return map.keySet() 280 .stream() 281 .map(ReferenceKey::get) 282 .filter(Objects::nonNull); 283 } 284 285 @Override 286 public Set<K> keySet() { 287 removeStaleReferences(); 288 return filterKeySet().collect(Collectors.toSet()); 289 } 290 291 @Override 292 public Collection<V> values() { 293 removeStaleReferences(); 294 return map.values(); 295 } 296 297 @Override 298 public Set<Entry<K, V>> entrySet() { 299 removeStaleReferences(); 300 return filterKeySet() 301 .map(k -> new AbstractMap.SimpleEntry<>(k, getNoCheckStale(k))) 302 .collect(Collectors.toSet()); 303 } 304 305 @Override 306 public V putIfAbsent(K key, V newValue) { 307 removeStaleReferences(); 308 ReferenceKey<K> entryKey = entryKey(key); 309 // If {@code putIfAbsent} returns non-null then was actually a 310 // {@code replace} and older key was used. In that case the new key was 311 // not used and the reference marked stale. 312 V oldValue = map.putIfAbsent(entryKey, newValue); 313 if (oldValue != null) { 314 entryKey.unused(); 315 } 316 return oldValue; 317 } 318 319 @Override 320 public boolean remove(Object key, Object value) { 321 // Rely on gc to clean up old key. 322 return map.remove(lookupKey(key), value); 323 } 324 325 @Override 326 public boolean replace(K key, V oldValue, V newValue) { 327 removeStaleReferences(); 328 // If replace is successful then the older key will be used and the 329 // lookup key will suffice. 330 return map.replace(lookupKey(key), oldValue, newValue); 331 } 332 333 @Override 334 public V replace(K key, V value) { 335 removeStaleReferences(); 336 // If replace is successful then the older key will be used and the 337 // lookup key will suffice. 338 return map.replace(lookupKey(key), value); 339 } 340 341 @Override 342 public String toString() { 343 removeStaleReferences(); 344 return filterKeySet() 345 .map(k -> k + "=" + getNoCheckStale(k)) 346 .collect(Collectors.joining(", ", "{", "}")); 347 } 348 349 /** 350 * Removes enqueued weak references from map. 351 */ 352 public void removeStaleReferences() { 353 while (true) { 354 Object key = stale.poll(); 355 if (key == null) { 356 break; 357 } 358 map.remove(key); 359 } 360 } 361 362 /** 363 * Puts an entry where the key and the value are the same. Used for 364 * interning values in a set. 365 * 366 * @implNote Requires a {@link ReferencedKeyMap} whose {@code V} type 367 * is a {@code ReferenceKey<K>}. Otherwise, a {@link ClassCastException} will 368 * be thrown. 369 * 370 * @param setMap {@link ReferencedKeyMap} where interning takes place 371 * @param key key to add 372 * 373 * @param <T> type of key 374 * 375 * @return the old key instance if found otherwise the new key instance 376 * 377 * @throws ClassCastException if {@code V} is not {@code EntryKey<T>} 378 */ 379 static <T> T intern(ReferencedKeyMap<T, ReferenceKey<T>> setMap, T key) { 380 T value = existingKey(setMap, key); 381 if (value != null) { 382 return value; 383 } 384 return internKey(setMap, key); 385 } 386 387 /** 388 * Puts an entry where the key and the value are the same. Used for 389 * interning values in a set. 390 * 391 * @implNote Requires a {@link ReferencedKeyMap} whose {@code V} type 392 * is a {@code ReferenceKey<K>}. Otherwise, a {@link ClassCastException} will 393 * be thrown. 394 * 395 * @param setMap {@link ReferencedKeyMap} where interning takes place 396 * @param key key to add 397 * @param interner operation to apply to key before adding to map 398 * 399 * @param <T> type of key 400 * 401 * @return the old key instance if found otherwise the new key instance 402 * 403 * @throws ClassCastException if {@code V} is not {@code EntryKey<T>} 404 * 405 * @implNote This version of intern should not be called during phase1 406 * using a lambda. Use an UnaryOperator instance instead. 407 */ 408 static <T> T intern(ReferencedKeyMap<T, ReferenceKey<T>> setMap, T key, UnaryOperator<T> interner) { 409 T value = existingKey(setMap, key); 410 if (value != null) { 411 return value; 412 } 413 key = interner.apply(key); 414 return internKey(setMap, key); 415 } 416 417 /** 418 * Check if the key already exists in the map. 419 * 420 * @param setMap {@link ReferencedKeyMap} where interning takes place 421 * @param key key to test 422 * 423 * @param <T> type of key 424 * 425 * @return key if found otherwise null 426 */ 427 private static <T> T existingKey(ReferencedKeyMap<T, ReferenceKey<T>> setMap, T key) { 428 setMap.removeStaleReferences(); 429 ReferenceKey<T> entryKey = setMap.get(setMap.lookupKey(key)); 430 return entryKey != null ? entryKey.get() : null; 431 } 432 433 /** 434 * Attempt to add key to map. 435 * 436 * @param setMap {@link ReferencedKeyMap} where interning takes place 437 * @param key key to add 438 * 439 * @param <T> type of key 440 * 441 * @return the old key instance if found otherwise the new key instance 442 */ 443 private static <T> T internKey(ReferencedKeyMap<T, ReferenceKey<T>> setMap, T key) { 444 ReferenceKey<T> entryKey = setMap.entryKey(key); 445 T interned; 446 do { 447 setMap.removeStaleReferences(); 448 ReferenceKey<T> existing = setMap.map.putIfAbsent(entryKey, entryKey); 449 if (existing == null) { 450 return key; 451 } else { 452 // If {@code putIfAbsent} returns non-null then was actually a 453 // {@code replace} and older key was used. In that case the new 454 // key was not used and the reference marked stale. 455 interned = existing.get(); 456 if (interned != null) { 457 entryKey.unused(); 458 } 459 } 460 } while (interned == null); 461 return interned; 462 } 463 464 465 /** 466 * Attempt to add key to map if absent. 467 * 468 * @param setMap {@link ReferencedKeyMap} where interning takes place 469 * @param key key to add 470 * 471 * @param <T> type of key 472 * 473 * @return true if the key was added 474 */ 475 static <T> boolean internAddKey(ReferencedKeyMap<T, ReferenceKey<T>> setMap, T key) { 476 ReferenceKey<T> entryKey = setMap.entryKey(key); 477 setMap.removeStaleReferences(); 478 ReferenceKey<T> existing = setMap.map.putIfAbsent(entryKey, entryKey); 479 if (existing == null) { 480 return true; 481 } else { 482 // If {@code putIfAbsent} returns non-null then was actually a 483 // {@code replace} and older key was used. In that case the new 484 // key was not used and the reference marked stale. 485 entryKey.unused(); 486 return false; 487 } 488 } 489 490 }