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