src/share/classes/java/util/HashMap.java

Print this page
rev 5810 : 8006593: Performance and compatibility improvements to hash based Map implementations.
Summary: Use ThreadLocalRandom for hash seed rather than shared Random. Initialize {HashMap|Hashtable}.hashSeed only as needed. Minor optimizations.
Reviewed-by: alanb, bchristi

@@ -190,21 +190,10 @@
     /**
      * holds values which can't be initialized until after VM is booted.
      */
     private static class Holder {
 
-            // Unsafe mechanics
-        /**
-         * Unsafe utilities
-         */
-        static final sun.misc.Unsafe UNSAFE;
-
-        /**
-         * Offset of "final" hashSeed field we must set in readObject() method.
-         */
-        static final long HASHSEED_OFFSET;
-
         /**
          * Table capacity above which to switch to use alternative hashing.
          */
         static final int ALTERNATIVE_HASHING_THRESHOLD;
 

@@ -228,33 +217,21 @@
                     throw new IllegalArgumentException("value must be positive integer.");
                 }
             } catch(IllegalArgumentException failed) {
                 throw new Error("Illegal value for 'jdk.map.althashing.threshold'", failed);
             }
-            ALTERNATIVE_HASHING_THRESHOLD = threshold;
 
-            try {
-                UNSAFE = sun.misc.Unsafe.getUnsafe();
-                HASHSEED_OFFSET = UNSAFE.objectFieldOffset(
-                    HashMap.class.getDeclaredField("hashSeed"));
-            } catch (NoSuchFieldException | SecurityException e) {
-                throw new Error("Failed to record hashSeed offset", e);
-            }
+            ALTERNATIVE_HASHING_THRESHOLD = threshold;
         }
     }
 
     /**
-     * If {@code true} then perform alternative hashing of String keys to reduce
-     * the incidence of collisions due to weak hash code calculation.
-     */
-    transient boolean useAltHashing;
-
-    /**
      * A randomizing value associated with this instance that is applied to
-     * hash code of keys to make hash collisions harder to find.
+     * hash code of keys to make hash collisions harder to find. If 0 then
+     * alternative hashing is disabled.
      */
-    transient final int hashSeed = sun.misc.Hashing.randomHashSeed(this);
+    transient int hashSeed = 0;
 
     /**
      * Constructs an empty <tt>HashMap</tt> with the specified initial
      * capacity and load factor.
      *

@@ -272,19 +249,19 @@
         if (loadFactor <= 0 || Float.isNaN(loadFactor))
             throw new IllegalArgumentException("Illegal load factor: " +
                                                loadFactor);
 
         // Find a power of 2 >= initialCapacity
-        int capacity = 1;
-        while (capacity < initialCapacity)
-            capacity <<= 1;
+        int capacity = (capacity = Integer.highestOneBit(initialCapacity)) != 0
+                ? capacity
+                : 1;
+        capacity <<= (Integer.bitCount(initialCapacity) > 1) ? 1 : 0;
 
         this.loadFactor = loadFactor;
         threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
         table = new Entry[capacity];
-        useAltHashing = sun.misc.VM.isBooted() &&
-                (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
+        initHashSeedAsNeeded(capacity);
         init();
     }
 
     /**
      * Constructs an empty <tt>HashMap</tt> with the specified initial

@@ -331,24 +308,38 @@
      */
     void init() {
     }
 
     /**
+     * Initialize the hashing mask value. We defer initialization until we
+     * really need it.
+     */
+    final boolean initHashSeedAsNeeded(int capacity) {
+        boolean currentAltHashing = hashSeed != 0;
+        boolean useAltHashing = sun.misc.VM.isBooted() &&
+                (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
+        boolean switching = currentAltHashing ^ useAltHashing;
+        if (switching) {
+            hashSeed = useAltHashing
+                ? sun.misc.Hashing.randomHashSeed(this)
+                : 0;
+        }
+        return switching;
+    }
+
+    /**
      * Retrieve object hash code and applies a supplemental hash function to the
      * result hash, which defends against poor quality hash functions.  This is
      * critical because HashMap uses power-of-two length hash tables, that
      * otherwise encounter collisions for hashCodes that do not differ
      * in lower bits. Note: Null keys always map to hash 0, thus index 0.
      */
     final int hash(Object k) {
-        int h = 0;
-        if (useAltHashing) {
-            if (k instanceof String) {
+        int h = hashSeed;
+        if (0 != h && k instanceof String) {
                 return sun.misc.Hashing.stringHash32((String) k);
             }
-            h = hashSeed;
-        }
 
         h ^= k.hashCode();
 
         // This function ensures that hashCodes that differ only by
         // constant multiples at each bit position have a bounded

@@ -555,15 +546,11 @@
             threshold = Integer.MAX_VALUE;
             return;
         }
 
         Entry[] newTable = new Entry[newCapacity];
-        boolean oldAltHashing = useAltHashing;
-        useAltHashing |= sun.misc.VM.isBooted() &&
-                (newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
-        boolean rehash = oldAltHashing ^ useAltHashing;
-        transfer(newTable, rehash);
+        transfer(newTable, initHashSeedAsNeeded(newCapacity));
         table = newTable;
         threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
     }
 
     /**

@@ -813,12 +800,11 @@
             }
             return false;
         }
 
         public final int hashCode() {
-            return (key==null   ? 0 : key.hashCode()) ^
-                   (value==null ? 0 : value.hashCode());
+            return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
         }
 
         public final String toString() {
             return getKey() + "=" + getValue();
         }

@@ -1115,14 +1101,10 @@
         s.defaultReadObject();
         if (loadFactor <= 0 || Float.isNaN(loadFactor))
             throw new InvalidObjectException("Illegal load factor: " +
                                                loadFactor);
 
-        // set hashSeed (can only happen after VM boot)
-        Holder.UNSAFE.putIntVolatile(this, Holder.HASHSEED_OFFSET,
-                sun.misc.Hashing.randomHashSeed(this));
-
         // Read in number of buckets and allocate the bucket array;
         s.readInt(); // ignored
 
         // Read number of mappings
         int mappings = s.readInt();

@@ -1134,20 +1116,19 @@
                 // capacity chosen by number of mappings
                 // and desired load (if >= 0.25)
                 mappings * Math.min(1 / loadFactor, 4.0f),
                 // we have limits...
                 HashMap.MAXIMUM_CAPACITY);
-        int capacity = 1;
         // find smallest power of two which holds all mappings
-        while (capacity < initialCapacity) {
-            capacity <<= 1;
-        }
+        int capacity = (capacity = Integer.highestOneBit(initialCapacity)) != 0
+                ? capacity
+                : 1;
+        capacity <<= (Integer.bitCount(initialCapacity) > 1) ? 1 : 0;
 
         table = new Entry[capacity];
         threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
-        useAltHashing = sun.misc.VM.isBooted() &&
-                (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
+        initHashSeedAsNeeded(capacity);
 
         init();  // Give subclass a chance to do its thing.
 
         // Read the keys and values, and put the mappings in the HashMap
         for (int i=0; i<mappings; i++) {