1 /*
2 * Copyright (c) 2023, 2025, 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 import jdk.internal.misc.CDS;
46
47 /**
48 * This class provides management of {@link Map maps} where it is desirable to
49 * remove entries automatically when the key is garbage collected. This is
50 * accomplished by using a backing map where the keys are either a
51 * {@link WeakReference} or a {@link SoftReference}.
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 new ReferencedKeyMap<K, V>(isSoft, supplier.get(), new ReferenceQueue<>());
147 }
148
149 /**
150 * {@return a key suitable for a map entry}
151 *
152 * @param key unwrapped key
153 */
154 @SuppressWarnings("unchecked")
155 private ReferenceKey<K> entryKey(Object key) {
156 if (isSoft) {
157 return new SoftReferenceKey<>((K)key, stale);
158 } else {
159 return new WeakReferenceKey<>((K)key, stale);
160 }
161 }
162
163 /**
164 * {@return a key suitable for lookup}
165 *
166 * @param key unwrapped key
167 */
168 @SuppressWarnings("unchecked")
169 private ReferenceKey<K> lookupKey(Object key) {
170 return new StrongReferenceKey<>((K)key);
171 }
172
173 @Override
174 public int size() {
175 removeStaleReferences();
176 return map.size();
177 }
178
179 @Override
180 public boolean isEmpty() {
181 removeStaleReferences();
182 return map.isEmpty();
183 }
184
185 @Override
186 public boolean containsKey(Object key) {
187 Objects.requireNonNull(key, "key must not be null");
188 removeStaleReferences();
189 return map.containsKey(lookupKey(key));
190 }
191
192 @Override
193 public boolean containsValue(Object value) {
194 Objects.requireNonNull(value, "value must not be null");
195 removeStaleReferences();
196 return map.containsValue(value);
197 }
198
199 @Override
200 public V get(Object key) {
201 removeStaleReferences();
202 return getNoCheckStale(key);
203 }
204
205 // Internal get(key) without removing stale references that would modify the keyset.
206 // Use when iterating or streaming over the keys to avoid ConcurrentModificationException.
207 private V getNoCheckStale(Object key) {
208 Objects.requireNonNull(key, "key must not be null");
209 return map.get(lookupKey(key));
210 }
211
212 @Override
213 public V put(K key, V newValue) {
214 Objects.requireNonNull(key, "key must not be null");
215 Objects.requireNonNull(newValue, "value must not be null");
216 removeStaleReferences();
217 ReferenceKey<K> entryKey = entryKey(key);
218 // If {@code put} returns non-null then was actually a {@code replace}
219 // and older key was used. In that case the new key was not used and the
220 // reference marked stale.
221 V oldValue = map.put(entryKey, newValue);
222 if (oldValue != null) {
223 entryKey.unused();
224 }
225 return oldValue;
226 }
227
228 @Override
229 public V remove(Object key) {
230 // Rely on gc to clean up old key.
231 return map.remove(lookupKey(key));
232 }
233
234 @Override
235 public void putAll(Map<? extends K, ? extends V> m) {
236 removeStaleReferences();
237 for (Entry<? extends K, ? extends V> entry : m.entrySet()) {
238 K key = entry.getKey();
239 V value = entry.getValue();
240 put(key, value);
241 }
242 }
243
244 @Override
245 public void clear() {
246 removeStaleReferences();
247 // Rely on gc to clean up old keys.
248 map.clear();
249 }
250
251 /**
252 * Common routine for collecting the current set of keys.
253 *
254 * @return {@link Stream} of valid keys (unwrapped)
255 */
256 private Stream<K> filterKeySet() {
257 return map.keySet()
258 .stream()
259 .map(ReferenceKey::get)
260 .filter(Objects::nonNull);
261 }
262
263 @Override
264 public Set<K> keySet() {
265 removeStaleReferences();
266 return filterKeySet().collect(Collectors.toSet());
267 }
268
269 @Override
270 public Collection<V> values() {
271 removeStaleReferences();
272 return map.values();
273 }
274
275 @Override
276 public Set<Entry<K, V>> entrySet() {
277 removeStaleReferences();
278 return filterKeySet()
279 .map(k -> new AbstractMap.SimpleEntry<>(k, getNoCheckStale(k)))
280 .collect(Collectors.toSet());
281 }
282
283 @Override
284 public V putIfAbsent(K key, V newValue) {
285 removeStaleReferences();
286 ReferenceKey<K> entryKey = entryKey(key);
287 // If {@code putIfAbsent} returns non-null then was actually a
288 // {@code replace} and older key was used. In that case the new key was
289 // not used and the reference marked stale.
290 V oldValue = map.putIfAbsent(entryKey, newValue);
291 if (oldValue != null) {
292 entryKey.unused();
293 }
294 return oldValue;
295 }
296
297 @Override
298 public boolean remove(Object key, Object value) {
299 // Rely on gc to clean up old key.
300 return map.remove(lookupKey(key), value);
301 }
302
303 @Override
304 public boolean replace(K key, V oldValue, V newValue) {
305 removeStaleReferences();
306 // If replace is successful then the older key will be used and the
307 // lookup key will suffice.
308 return map.replace(lookupKey(key), oldValue, newValue);
309 }
310
311 @Override
312 public V replace(K key, V value) {
313 removeStaleReferences();
314 // If replace is successful then the older key will be used and the
315 // lookup key will suffice.
316 return map.replace(lookupKey(key), value);
317 }
318
319 @Override
320 public String toString() {
321 removeStaleReferences();
322 return filterKeySet()
323 .map(k -> k + "=" + getNoCheckStale(k))
324 .collect(Collectors.joining(", ", "{", "}"));
325 }
326
327 /**
328 * Removes enqueued weak references from map.
329 */
330 public void removeStaleReferences() {
331 while (true) {
332 Object key = stale.poll();
333 if (key == null) {
334 break;
335 }
336 map.remove(key);
337 }
338 }
339
340 @SuppressWarnings("unchecked")
341 public void prepareForAOTCache() {
342 // We are running the AOT assembly phase. The JVM has a single Java thread, so
343 // we don't have any concurrent threads that may modify the map while we are
344 // iterating its keys.
345 //
346 // Also, the java.lang.ref.Reference$ReferenceHandler thread is not running,
347 // so even if the GC has put some of the keys on the pending ReferencePendingList,
348 // none of the keys would have been added to the stale queue yet.
349 assert CDS.isSingleThreadVM();
350
351 for (ReferenceKey<K> key : map.keySet()) {
352 Object referent = key.get();
353 if (referent == null) {
354 // We don't need this key anymore. Add to stale queue
355 ((Reference)key).enqueue();
356 } else {
357 // Make sure the referent cannot be collected. Otherwise, when
358 // the referent is collected, the GC may push the key onto
359 // Universe::reference_pending_list() at an unpredictable time,
360 // making it difficult to correctly serialize the key's
361 // state into the CDS archive.
362 //
363 // See aotReferenceObjSupport.cpp for more info.
364 CDS.keepAlive(referent);
365 }
366 Reference.reachabilityFence(referent);
367 }
368
369 // Remove all keys enqueued above
370 removeStaleReferences();
371 }
372
373
374 /**
375 * Puts an entry where the key and the value are the same. Used for
376 * interning values in a set.
377 *
378 * @implNote Requires a {@link ReferencedKeyMap} whose {@code V} type
379 * is a {@code ReferenceKey<K>}. Otherwise, a {@link ClassCastException} will
380 * be thrown.
381 *
382 * @param setMap {@link ReferencedKeyMap} where interning takes place
383 * @param key key to add
384 *
385 * @param <T> type of key
386 *
387 * @return the old key instance if found otherwise the new key instance
388 *
389 * @throws ClassCastException if {@code V} is not {@code EntryKey<T>}
390 */
391 static <T> T intern(ReferencedKeyMap<T, ReferenceKey<T>> setMap, T key) {
392 T value = existingKey(setMap, key);
393 if (value != null) {
394 return value;
395 }
396 return internKey(setMap, key);
397 }
398
399 /**
400 * Puts an entry where the key and the value are the same. Used for
401 * interning values in a set.
402 *
403 * @implNote Requires a {@link ReferencedKeyMap} whose {@code V} type
404 * is a {@code ReferenceKey<K>}. Otherwise, a {@link ClassCastException} will
405 * be thrown.
406 *
407 * @param setMap {@link ReferencedKeyMap} where interning takes place
408 * @param key key to add
409 * @param interner operation to apply to key before adding to map
410 *
411 * @param <T> type of key
412 *
413 * @return the old key instance if found otherwise the new key instance
414 *
415 * @throws ClassCastException if {@code V} is not {@code EntryKey<T>}
416 *
417 * @implNote This version of intern should not be called during phase1
418 * using a lambda. Use an UnaryOperator instance instead.
419 */
420 static <T> T intern(ReferencedKeyMap<T, ReferenceKey<T>> setMap, T key, UnaryOperator<T> interner) {
421 T value = existingKey(setMap, key);
422 if (value != null) {
423 return value;
424 }
425 key = interner.apply(key);
426 return internKey(setMap, key);
427 }
428
429 /**
430 * Check if the key already exists in the map.
431 *
432 * @param setMap {@link ReferencedKeyMap} where interning takes place
433 * @param key key to test
434 *
435 * @param <T> type of key
436 *
437 * @return key if found otherwise null
438 */
439 private static <T> T existingKey(ReferencedKeyMap<T, ReferenceKey<T>> setMap, T key) {
440 setMap.removeStaleReferences();
441 ReferenceKey<T> entryKey = setMap.get(setMap.lookupKey(key));
442 return entryKey != null ? entryKey.get() : null;
443 }
444
445 /**
446 * Attempt to add key to map.
447 *
448 * @param setMap {@link ReferencedKeyMap} where interning takes place
449 * @param key key to add
450 *
451 * @param <T> type of key
452 *
453 * @return the old key instance if found otherwise the new key instance
454 */
455 private static <T> T internKey(ReferencedKeyMap<T, ReferenceKey<T>> setMap, T key) {
456 ReferenceKey<T> entryKey = setMap.entryKey(key);
457 T interned;
458 do {
459 setMap.removeStaleReferences();
460 ReferenceKey<T> existing = setMap.map.putIfAbsent(entryKey, entryKey);
461 if (existing == null) {
462 return key;
463 } else {
464 // If {@code putIfAbsent} returns non-null then was actually a
465 // {@code replace} and older key was used. In that case the new
466 // key was not used and the reference marked stale.
467 interned = existing.get();
468 if (interned != null) {
469 entryKey.unused();
470 }
471 }
472 } while (interned == null);
473 return interned;
474 }
475
476
477 /**
478 * Attempt to add key to map if absent.
479 *
480 * @param setMap {@link ReferencedKeyMap} where interning takes place
481 * @param key key to add
482 *
483 * @param <T> type of key
484 *
485 * @return true if the key was added
486 */
487 static <T> boolean internAddKey(ReferencedKeyMap<T, ReferenceKey<T>> setMap, T key) {
488 ReferenceKey<T> entryKey = setMap.entryKey(key);
489 setMap.removeStaleReferences();
490 ReferenceKey<T> existing = setMap.map.putIfAbsent(entryKey, entryKey);
491 if (existing == null) {
492 return true;
493 } else {
494 // If {@code putIfAbsent} returns non-null then was actually a
495 // {@code replace} and older key was used. In that case the new
496 // key was not used and the reference marked stale.
497 entryKey.unused();
498 return false;
499 }
500 }
501
502 }