diff a/src/java.base/share/classes/java/util/WeakHashMap.java b/src/java.base/share/classes/java/util/WeakHashMap.java
--- a/src/java.base/share/classes/java/util/WeakHashMap.java
+++ b/src/java.base/share/classes/java/util/WeakHashMap.java
@@ -23,10 +23,13 @@
  * questions.
  */
 
 package java.util;
 
+import sun.security.action.GetPropertyAction;
+
+import java.lang.ref.SoftReference;
 import java.lang.ref.WeakReference;
 import java.lang.ref.ReferenceQueue;
 import java.util.function.BiConsumer;
 import java.util.function.BiFunction;
 import java.util.function.Consumer;
@@ -34,37 +37,58 @@
 
 /**
  * Hash table based implementation of the {@code Map} interface, with
  * <em>weak keys</em>.
  * An entry in a {@code WeakHashMap} will automatically be removed when
- * its key is no longer in ordinary use.  More precisely, the presence of a
+ * its key is no longer in ordinary use.
+ * More precisely, for keys that are identity objects, the presence of a
  * mapping for a given key will not prevent the key from being discarded by the
  * garbage collector, that is, made finalizable, finalized, and then reclaimed.
+ * For keys that are {@linkplain Class#isValue() value objects}, the retention of the
+ * key and value depends on the {@link ValuePolicy} for the {@code WeakHashMap} and
+ * the garbage collection handling of objects that are linked by {@link SoftReference}.
  * When a key has been discarded its entry is effectively removed from the map,
  * so this class behaves somewhat differently from other {@code Map}
  * implementations.
  *
+ * <p>
+ * Keys that are {@linkplain Class#isValue() value objects} do not have identity and cannot be
+ * the referent in any {@link java.lang.ref.Reference} including {@link WeakReference}.
+ * The retention of entries with keys that are value objects is selected
+ * using {@link ValuePolicy} when the {@code WeakHashMap} is created.
+ * The default {@code ValuePolicy} is {@link ValuePolicy#defaultValuePolicy()}.
+ * The retention modes implemented by {@link #put(Object, Object) WeakHashMap.put(k,v)} are:
+ * <UL>
+ *     <LI> {@linkplain ValuePolicy#STRONG SOFT} - entries have a lifetime similar to
+ *          referents of {@link SoftReference},
+ *     <LI> {@linkplain ValuePolicy#STRONG STRONG} - entries are retained until removed,
+ *     <LI> {@linkplain ValuePolicy#STRONG DISCARD} - entries are discarded and not put in the map,
+ *     <LI> {@linkplain ValuePolicy#STRONG THROW} - entries are not inserted and
+ *          {@link #put(Object, Object) put(k,v)} throws {@link IdentityException}
+ * </UL>
+ *
  * <p> Both null values and the null key are supported. This class has
  * performance characteristics similar to those of the {@code HashMap}
  * class, and has the same efficiency parameters of <em>initial capacity</em>
  * and <em>load factor</em>.
  *
  * <p> Like most collection classes, this class is not synchronized.
  * A synchronized {@code WeakHashMap} may be constructed using the
  * {@link Collections#synchronizedMap Collections.synchronizedMap}
  * method.
  *
- * <p> This class is intended primarily for use with key objects whose
+ * <p> <i>Update needed for Value Objects:
+ * <br>This class is intended primarily for use with key objects whose
  * {@code equals} methods test for object identity using the
  * {@code ==} operator.  Once such a key is discarded it can never be
  * recreated, so it is impossible to do a lookup of that key in a
  * {@code WeakHashMap} at some later time and be surprised that its entry
  * has been removed.  This class will work perfectly well with key objects
  * whose {@code equals} methods are not based upon object identity, such
  * as {@code String} instances.  With such recreatable key objects,
  * however, the automatic removal of {@code WeakHashMap} entries whose
- * keys have been discarded may prove to be confusing.
+ * keys have been discarded may prove to be confusing.</i>
  *
  * <p> The behavior of the {@code WeakHashMap} class depends in part upon
  * the actions of the garbage collector, so several familiar (though not
  * required) {@code Map} invariants do not hold for this class.  Because
  * the garbage collector may discard keys at any time, a
@@ -187,18 +211,23 @@
      *
      * @see ConcurrentModificationException
      */
     int modCount;
 
+    // Current policy with regard to keys that are Value classes.
+    private final ValuePolicy valuePolicy;
+
     @SuppressWarnings("unchecked")
     private Entry<K,V>[] newTable(int n) {
         return (Entry<K,V>[]) new Entry<?,?>[n];
     }
 
     /**
      * Constructs a new, empty {@code WeakHashMap} with the given initial
      * capacity and the given load factor.
+     * The {@code WeakHashMap} is created using the {@linkplain ValuePolicy#defaultValuePolicy()
+     * default policy for value objects}.
      *
      * @apiNote
      * To create a {@code WeakHashMap} with an initial capacity that accommodates
      * an expected number of mappings, use {@link #newWeakHashMap(int) newWeakHashMap}.
      *
@@ -206,28 +235,46 @@
      * @param  loadFactor      The load factor of the {@code WeakHashMap}
      * @throws IllegalArgumentException if the initial capacity is negative,
      *         or if the load factor is nonpositive.
      */
     public WeakHashMap(int initialCapacity, float loadFactor) {
+        this(initialCapacity, loadFactor, ValuePolicy.DEFAULT_VALUE_POLICY);
+    }
+
+    /**
+     * Constructs a new, empty {@code WeakHashMap} with the given initial
+     * capacity, the given load factor, and value policy.
+     *
+     * @param  initialCapacity The initial capacity of the {@code WeakHashMap}
+     * @param  loadFactor      The load factor of the {@code WeakHashMap}
+     * @param  valuePolicy     The {@link ValuePolicy} for keys that are value objects
+     * @throws IllegalArgumentException if the initial capacity is negative,
+     *         or if the load factor is nonpositive.
+     * @throws NullPointerException if {@code valuePolicy} is null
+     */
+    public WeakHashMap(int initialCapacity, float loadFactor, ValuePolicy valuePolicy) {
         if (initialCapacity < 0)
             throw new IllegalArgumentException("Illegal Initial Capacity: "+
                                                initialCapacity);
         if (initialCapacity > MAXIMUM_CAPACITY)
             initialCapacity = MAXIMUM_CAPACITY;
 
         if (loadFactor <= 0 || Float.isNaN(loadFactor))
             throw new IllegalArgumentException("Illegal Load factor: "+
                                                loadFactor);
+        this.valuePolicy = Objects.requireNonNull(valuePolicy, "valuePolicy");
         int capacity = HashMap.tableSizeFor(initialCapacity);
         table = newTable(capacity);
         this.loadFactor = loadFactor;
         threshold = (int)(capacity * loadFactor);
     }
 
     /**
      * Constructs a new, empty {@code WeakHashMap} with the given initial
      * capacity and the default load factor (0.75).
+     * The {@code WeakHashMap} is created using the {@linkplain ValuePolicy#defaultValuePolicy()
+     * default policy for value objects}.
      *
      * @apiNote
      * To create a {@code WeakHashMap} with an initial capacity that accommodates
      * an expected number of mappings, use {@link #newWeakHashMap(int) newWeakHashMap}.
      *
@@ -239,20 +286,35 @@
     }
 
     /**
      * Constructs a new, empty {@code WeakHashMap} with the default initial
      * capacity (16) and load factor (0.75).
+     * The {@code WeakHashMap} is created using the {@linkplain ValuePolicy#defaultValuePolicy()
+     * default policy for value objects}.
      */
     public WeakHashMap() {
         this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
     }
 
+    /**
+     * Constructs a new, empty {@code WeakHashMap} with the {@link ValuePolicy},
+     * the default initial capacity (16) and load factor (0.75).
+     *
+     * @param  valuePolicy     The {@link ValuePolicy} for keys that are value objects; non-null
+     * @throws NullPointerException if {@code valuePolicy} is null
+     */
+    public WeakHashMap(ValuePolicy valuePolicy) {
+        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, valuePolicy);
+    }
+
     /**
      * Constructs a new {@code WeakHashMap} with the same mappings as the
      * specified map.  The {@code WeakHashMap} is created with the default
      * load factor (0.75) and an initial capacity sufficient to hold the
      * mappings in the specified map.
+     * The {@code WeakHashMap} is created using the {@linkplain ValuePolicy#defaultValuePolicy()
+     * default policy for value objects}.
      *
      * @param   m the map whose mappings are to be placed in this map
      * @throws  NullPointerException if the specified map is null
      * @since   1.3
      */
@@ -261,10 +323,17 @@
                 DEFAULT_INITIAL_CAPACITY),
              DEFAULT_LOAD_FACTOR);
         putAll(m);
     }
 
+    /**
+     * {@return the {@link ValuePolicy} for this WeakHashMap.}
+     */
+    public ValuePolicy valuePolicy() {
+        return valuePolicy;
+    }
+
     // internal utilities
 
     /**
      * Value representing null keys inside tables.
      */
@@ -289,11 +358,12 @@
      * default uses Object.equals.
      */
     private boolean matchesKey(Entry<K,V> e, Object key) {
         // check if the given entry refers to the given key without
         // keeping a strong reference to the entry's referent
-        if (e.refersTo(key)) return true;
+        // only identity objects can be compared to a reference
+        if (Objects.isIdentityObject(key) && e.refersTo(key)) return true;
 
         // then check for equality if the referent is not cleared
         Object k = e.get();
         return k != null && key.equals(k);
     }
@@ -453,13 +523,20 @@
      * @param value value to be associated with the specified key.
      * @return the previous value associated with {@code key}, or
      *         {@code null} if there was no mapping for {@code key}.
      *         (A {@code null} return can also indicate that the map
      *         previously associated {@code null} with {@code key}.)
+     * @throws IdentityException if {@code key} is a value object
+     *         and the {@link #valuePolicy() valuePolicy} is {@link ValuePolicy#THROW}.
      */
     public V put(K key, V value) {
         Object k = maskNull(key);
+        final boolean isValue = Objects.isValueObject(k);
+        if (isValue && valuePolicy == ValuePolicy.DISCARD) {
+            // put of a value object key with value policy DISCARD is more like remove(key)
+            return remove(key);
+        }
         int h = hash(k);
         Entry<K,V>[] tab = getTable();
         int i = indexFor(h, tab.length);
 
         for (Entry<K,V> e = tab[i]; e != null; e = e.next) {
@@ -469,18 +546,49 @@
                     e.value = value;
                 return oldValue;
             }
         }
 
-        modCount++;
         Entry<K,V> e = tab[i];
-        tab[i] = new Entry<>(k, value, queue, h, e);
+        e = (isValue) ? newValueEntry(k, value, queue, h, e) : new Entry<>(k, value, queue, h, e);
+
+        modCount++;
+        tab[i] = e;
         if (++size > threshold)
             resize(tab.length * 2);
         return null;
     }
 
+    /**
+     * Return a new entry for keys that are value objects.
+     * The {@link ValuePolicy} for this WeakHashMap determines what entry is returned.
+     * <ul>
+     *     <li> THROW - Throws an IdentityException</li>
+     *     <li> STRONG - a StrongEntry </li>
+     *     <li> SOFT - a SoftEntry</li>
+     *     <li> DISCARD - null</li>
+     * </ul>
+     *
+     * @param key key with which the specified value is to be associated; non-null
+     * @param value value to be associated with the specified key
+     * @param queue queue
+     * @param hash hash
+     * @param next next
+     * @return a new entry or null to discard
+     * @throws IdentityException if the valuePolicy is {@link ValuePolicy#THROW}
+     */
+    private Entry<K, V> newValueEntry(Object key, V value,
+                                      ReferenceQueue<Object> queue,
+                                      int hash, Entry<K,V> next) {
+        return switch (valuePolicy) {
+            case THROW -> throw new IdentityException(key.getClass());
+            case STRONG -> StrongEntry.newStrongEntry(key, value, queue, hash,  next);
+            case SOFT ->  SoftEntry.newSoftEntry(key, value, queue, hash,  next);
+            case DISCARD -> null;
+        };
+    }
+
     /**
      * Rehashes the contents of this map into a new array with a
      * larger capacity.  This method is called automatically when the
      * number of keys in this map reaches its threshold.
      *
@@ -545,10 +653,12 @@
      * These mappings will replace any mappings that this map had for any
      * of the keys currently in the specified map.
      *
      * @param m mappings to be stored in this map.
      * @throws  NullPointerException if the specified map is null.
+     * @throws  IdentityException if any of the {@code keys} is a value object
+     *         and the {@link #valuePolicy() valuePolicy} is {@link ValuePolicy#THROW}.
      */
     public void putAll(Map<? extends K, ? extends V> m) {
         int numKeysToBeAdded = m.size();
         if (numKeysToBeAdded == 0)
             return;
@@ -575,10 +685,28 @@
 
         for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
             put(e.getKey(), e.getValue());
     }
 
+    /**
+     * {@inheritDoc}
+     * @param key {@inheritDoc}
+     * @param value {@inheritDoc}
+     * @return {@inheritDoc}
+     *
+     * @throws  IdentityException if {@code key} is a value object
+     *         and the {@link #valuePolicy() valuePolicy} is {@link ValuePolicy#THROW}.
+     */
+    public V putIfAbsent(K key, V value) {
+        V v = get(key);
+        if (v == null) {
+            v = put(key, value);
+        }
+
+        return v;
+    }
+
     /**
      * Removes the mapping for a key from this weak hash map if it is present.
      * More formally, if this map contains a mapping from key {@code k} to
      * value {@code v} such that <code>(key==null ?  k==null :
      * key.equals(k))</code>, that mapping is removed.  (The map can contain
@@ -764,10 +892,100 @@
         public String toString() {
             return getKey() + "=" + getValue();
         }
     }
 
+    /**
+     * A SoftEntry is used for value class keys in which the entries are retained
+     * until there is some memory pressure.  An anchor object is used as the referent
+     * of a SoftReference and also as the referent of the WeakReference.
+     * After the SoftReference is cleared, due to GC pressure, the WeakReference is cleared too.
+     *
+     * @param <K> key
+     * @param <V> value
+     */
+    private static class SoftEntry<K, V> extends Entry<K, V> {
+        final Object realKey;
+        // SoftReference to the anchor to keep it alive until GC clears the SoftReference
+        private final SoftReference<Object> softAnchor;
+
+        static <K, V> SoftEntry<K, V> newSoftEntry(Object key, V value,
+                                      ReferenceQueue<Object> queue,
+                                      int hash, Entry<K, V> next) {
+            // Select a new anchor object; the entry will be retained until the anchor is collected
+            Object anchor = new Object();
+            return new SoftEntry<>(anchor, key, value, queue, hash, next);
+        }
+
+        private SoftEntry(Object anchor, Object key, V value,
+                    ReferenceQueue<Object> queue,
+                    int hash, Entry<K,V> next) {
+            super(anchor, value, queue, hash, next);
+            this.realKey = key;
+            this.softAnchor = new SoftReference<>(anchor);
+        }
+
+        /**
+         * The real key is not the referent.
+         * {{@inheritDoc}}
+         */
+        @Override
+        @SuppressWarnings("unchecked")
+        public K get() {
+            return (K) realKey;
+        }
+
+        @SuppressWarnings("unchecked")
+        public K getKey() {
+            return (K) realKey;
+        }
+    }
+
+    /**
+     * A StrongEntry is used for value class keys in which the entries are retained
+     * until removed.  A singleton instance is used as the referent of the WeakReference.
+     * Since the anchor is never reclaimed, the Entry is retained forever.
+     *
+     * @param <K> key
+     * @param <V> value
+     */
+    private static class StrongEntry<K, V> extends Entry<K, V> {
+        final Object realKey;
+
+        // A permanent strong reference to an Object
+        private static final Object STRONG_ANCHOR = new Object();
+
+        static <K, V> StrongEntry<K, V> newStrongEntry(Object key, V value,
+                                      ReferenceQueue<Object> queue,
+                                      int hash, Entry<K, V> next) {
+            return new StrongEntry<>(STRONG_ANCHOR, key, value, queue, hash, next);
+        }
+
+        private StrongEntry(Object anchor, Object key, V value,
+                    ReferenceQueue<Object> queue,
+                    int hash, Entry<K,V> next) {
+            super(anchor, value, queue, hash, next);
+            this.realKey = key;
+        }
+
+        /**
+         * The real key is not the referent.
+         * {{@inheritDoc}}
+         */
+        @Override
+        @SuppressWarnings("unchecked")
+        public K get() {
+            return (K) realKey;
+        }
+
+
+        @SuppressWarnings("unchecked")
+        public K getKey() {
+            return (K) realKey;
+        }
+    }
+
     private abstract class HashIterator<T> implements Iterator<T> {
         private int index;
         private Entry<K,V> entry;
         private Entry<K,V> lastReturned;
         private int expectedModCount = modCount;
@@ -1361,6 +1579,73 @@
             throw new IllegalArgumentException("Negative number of mappings: " + numMappings);
         }
         return new WeakHashMap<>(HashMap.calculateHashMapCapacity(numMappings));
     }
 
+    /**
+     * Enum for the ValuePolicy; when putting a key and value into a WeakHashMap
+     * determines how keys that are value objects are retained (or not).
+     * The default {@code ValuePolicy} is {@link ValuePolicy#SOFT}.
+     * @since Valhalla
+     */
+    public enum ValuePolicy {
+        /**
+         * If the key is a value object, retain the key and value until removed or
+         * there is memory pressure that causes soft references to be cleared.
+         */
+        SOFT,
+        /**
+         * If the key is a value object, retain the key and value until removed.
+         */
+        STRONG,
+        /**
+         * If the key is a value object, discard the key and value immediately;
+         * such keys and values are not retained.
+         */
+        DISCARD,
+        /**
+         * If the key is a value object, throw {@link IdentityException};
+         * such keys and values are not retained.
+         */
+        THROW;
+
+        /**
+         * {@return the default ValuePolicy}
+         *
+         * The default {@code ValuePolicy} is {@link ValuePolicy#SOFT} unless overridden by
+         * the system property {@systemProperty java.util.WeakHashMap.valueKeyRetention}.
+         * If the property is set to the name of a {@code ValuePolicy} enum,
+         * the default {@code ValuePolicy} is set using {@link ValuePolicy#valueOf(String)}.
+         * If the property value is absent or not valid, the policy is set to {@link ValuePolicy#SOFT}.
+         */
+        public static ValuePolicy defaultValuePolicy() {
+            return DEFAULT_VALUE_POLICY;
+        }
+
+        // System property name for the default ValuePolicy
+        private static final String WEAK_HASH_MAP_VALUE_KEY_RETENTION =
+                "java.util.WeakHashMap.valueKeyRetention";
+
+        // Default WeakHashMap ValuePolicy for keys that are value objects
+        private static final ValuePolicy DEFAULT_VALUE_POLICY = initDefaultValuePolicy();
+
+        /**
+         * {@return the default policy for retention of keys that are value classes}
+         * If the system property "java.util.WeakHashMap.valueKeyRetention"
+         * is the name of a {@link ValuePolicy} enum return it,
+         * otherwise return {@link ValuePolicy#SOFT}.
+         */
+        private static ValuePolicy initDefaultValuePolicy() {
+            try {
+                String p = GetPropertyAction
+                        .privilegedGetProperty(WEAK_HASH_MAP_VALUE_KEY_RETENTION);
+                if (p != null) {
+                    return ValuePolicy.valueOf(p);
+                }
+            } catch (IllegalArgumentException ex) {
+            }
+
+            return SOFT;  // hardcoded default if property not set
+        }
+    }
+
 }
