--- old/src/share/classes/java/util/HashMap.java 2013-03-04 11:57:38.453059653 -0800 +++ new/src/share/classes/java/util/HashMap.java 2013-03-04 11:57:38.273059660 -0800 @@ -250,9 +250,10 @@ /** * 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. Initialized via + * sun.misc.Unsafe when needed. */ - transient final int hashSeed = sun.misc.Hashing.randomHashSeed(this); + transient final int hashSeed = 0; /** * Constructs an empty HashMap with the specified initial @@ -274,15 +275,17 @@ 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(); init(); } @@ -333,6 +336,18 @@ } /** + * Initialize the hashing mask value. We defer initialization until we + * really need it. + */ + final void initHashSeedAsNeeded() { + if (useAltHashing) { + // set hashSeed (can only happen after VM boot) + Holder.UNSAFE.putIntVolatile(this, Holder.HASHSEED_OFFSET, + sun.misc.Hashing.randomHashSeed(this)); + } + } + + /** * 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 @@ -561,6 +576,9 @@ useAltHashing |= sun.misc.VM.isBooted() && (newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD); boolean rehash = oldAltHashing ^ useAltHashing; + if(rehash) { + initHashSeedAsNeeded(); + } transfer(newTable, rehash); table = newTable; threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1); @@ -815,8 +833,7 @@ } public final int hashCode() { - return (key==null ? 0 : key.hashCode()) ^ - (value==null ? 0 : value.hashCode()); + return hash ^ Objects.hashCode(getValue()); } public final String toString() { @@ -1117,10 +1134,6 @@ 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 @@ -1136,16 +1149,17 @@ 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(); init(); // Give subclass a chance to do its thing. --- old/src/share/classes/java/util/Hashtable.java 2013-03-04 11:57:39.105059622 -0800 +++ new/src/share/classes/java/util/Hashtable.java 2013-03-04 11:57:38.929059631 -0800 @@ -241,28 +241,31 @@ /** * 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. Initialized via + * sun.misc.Unsafe when needed. */ - transient final int hashSeed = sun.misc.Hashing.randomHashSeed(this); + transient final int hashSeed = 0; - private int hash(Object k) { + /** + * Initialize the hashing mask value. + */ + final void initHashSeedAsNeeded() { if (useAltHashing) { - if (k.getClass() == String.class) { - return sun.misc.Hashing.stringHash32((String) k); - } else { - int h = hashSeed ^ k.hashCode(); - - // This function ensures that hashCodes that differ only by - // constant multiples at each bit position have a bounded - // number of collisions (approximately 8 at default load factor). - h ^= (h >>> 20) ^ (h >>> 12); - return h ^ (h >>> 7) ^ (h >>> 4); - } - } else { - return k.hashCode(); + // set hashSeed (can only happen after VM boot) + UNSAFE.putIntVolatile(this, HASHSEED_OFFSET, + sun.misc.Hashing.randomHashSeed(this)); + } else if((hashSeed != 0) && sun.misc.VM.isBooted()) { + // force it back to zero if we can. + UNSAFE.putIntVolatile(this, HASHSEED_OFFSET, 0); } } + private int hash(Object k) { + // We don't test for useAltHashing because hashSeed will be zero if + // alternative hashing is disabled. + return hashSeed ^ k.hashCode(); + } + /** * Constructs a new, empty hashtable with the specified initial * capacity and the specified load factor. @@ -286,6 +289,7 @@ threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1); useAltHashing = sun.misc.VM.isBooted() && (initialCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD); + initHashSeedAsNeeded(); } /** @@ -501,6 +505,9 @@ useAltHashing = sun.misc.VM.isBooted() && (newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD); boolean rehash = currentAltHashing ^ useAltHashing; + if(rehash) { + initHashSeedAsNeeded(); + } table = newMap; @@ -999,10 +1006,6 @@ // Read in the length, threshold, and loadfactor s.defaultReadObject(); - // set hashSeed - UNSAFE.putIntVolatile(this, HASHSEED_OFFSET, - sun.misc.Hashing.randomHashSeed(this)); - // Read the original length of the array and number of elements int origlength = s.readInt(); int elements = s.readInt(); @@ -1022,6 +1025,7 @@ count = 0; useAltHashing = sun.misc.VM.isBooted() && (length >= Holder.ALTERNATIVE_HASHING_THRESHOLD); + initHashSeedAsNeeded(); // Read the number of elements and then all the key/value objects for (; elements > 0; elements--) { --- old/src/share/classes/sun/misc/Hashing.java 2013-03-04 11:57:39.757059593 -0800 +++ new/src/share/classes/sun/misc/Hashing.java 2013-03-04 11:57:39.577059601 -0800 @@ -24,7 +24,7 @@ */ package sun.misc; -import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; /** * Hashing utilities. @@ -213,18 +213,6 @@ private static class Holder { /** - * Used for generating per-instance hash seeds. - * - * We try to improve upon the default seeding. - */ - static final Random SEED_MAKER = new Random( - Double.doubleToRawLongBits(Math.random()) - ^ System.identityHashCode(Hashing.class) - ^ System.currentTimeMillis() - ^ System.nanoTime() - ^ Runtime.getRuntime().freeMemory()); - - /** * Access to {@code String.hash32()} */ static final JavaLangAccess LANG_ACCESS; @@ -248,10 +236,17 @@ return Holder.LANG_ACCESS.getStringHash32(string); } + /** + * Return a non-zero 32-bit pseudo random value. The {@code instance} object + * may be used as part of the value. + * + * @param instance an object to use if desired in choosing value. + * @return a non-zero 32-bit pseudo random value. + */ public static int randomHashSeed(Object instance) { int seed; if (sun.misc.VM.isBooted()) { - seed = Holder.SEED_MAKER.nextInt(); + seed = ThreadLocalRandom.current().nextInt(); } else { // lower quality "random" seed value--still better than zero and not // not practically reversible.