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