--- old/src/share/classes/java/util/WeakHashMap.java 2012-05-22 21:33:16.000000000 -0700
+++ new/src/share/classes/java/util/WeakHashMap.java 2012-05-22 21:33:16.000000000 -0700
@@ -183,7 +183,67 @@
* @see ConcurrentModificationException
*/
int modCount;
+
+ /**
+ * The default threshold of capacity above which alternate hashing is
+ * used. Alternative hashing reduces the incidence of collisions due to
+ * weak hash code calculation.
+ *
+ * This value may be overridden by defining the system property
+ * {@code java.util.althashing.threshold} to an integer value. A property
+ * value of {@code 1} forces alternative hashing to be used at all times
+ * whereas {@code 2147483648 } ({@code Integer.MAX_VALUE}) value ensures
+ * that alternative hashing is never used.
+ */
+ static final int ALTERNATE_HASHING_THRESHOLD_DEFAULT = 0;
+
+ /**
+ * holds values which can't be initialized until after VM is booted.
+ */
+ private static class Holder {
+
+ /**
+ * Table capacity above which to switch to use alternate hashing.
+ */
+ static final int ALTERNATE_HASHING_THRESHOLD;
+
+ static {
+ String altThreshold = java.security.AccessController.doPrivileged(
+ new sun.security.action.GetPropertyAction(
+ "jdk.map.althashing.threshold"));
+
+ int threshold;
+ try {
+ threshold = (null != altThreshold)
+ ? Integer.parseInt(altThreshold)
+ : ALTERNATE_HASHING_THRESHOLD_DEFAULT;
+
+ if(threshold == -1) {
+ threshold = Integer.MAX_VALUE;
+ }
+ if(threshold < 0) {
+ throw new IllegalArgumentException("value must be positive integer.");
+ }
+ } catch(IllegalArgumentException failed) {
+ throw new Error("Illegal value for 'jdk.map.althashing.threshold'", failed);
+ }
+ ALTERNATE_HASHING_THRESHOLD = threshold;
+ }
+ }
+
+ /**
+ * If {@code true} then perform alternate hashing to reduce the incidence of
+ * collisions due to weak hash code calculation.
+ */
+ transient boolean useAltHashing;
+
+ /**
+ * A random mask value that is used for hashcode values associated with this
+ * instance to make hash collisions harder to find.
+ */
+ transient final int hashMask = sun.misc.Hashing.makeHashMask(this);
+
@SuppressWarnings("unchecked")
private Entry[] newTable(int n) {
return (Entry[]) new Entry[n];
@@ -214,6 +274,8 @@
table = newTable(capacity);
this.loadFactor = loadFactor;
threshold = (int)(capacity * loadFactor);
+ useAltHashing = sun.misc.VM.isBooted() &&
+ (capacity >= Holder.ALTERNATE_HASHING_THRESHOLD);
}
/**
@@ -232,9 +294,7 @@
* capacity (16) and load factor (0.75).
*/
public WeakHashMap() {
- this.loadFactor = DEFAULT_LOAD_FACTOR;
- threshold = DEFAULT_INITIAL_CAPACITY;
- table = newTable(DEFAULT_INITIAL_CAPACITY);
+ this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
/**
@@ -248,7 +308,8 @@
* @since 1.3
*/
public WeakHashMap(Map extends K, ? extends V> m) {
- this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, 16),
+ this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
+ DEFAULT_INITIAL_CAPACITY),
DEFAULT_LOAD_FACTOR);
putAll(m);
}
@@ -283,6 +344,39 @@
}
/**
+ * 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.
+ */
+ int hash(Object k) {
+ if (null == k) {
+ return 0;
+ }
+
+ int h;
+ if (useAltHashing) {
+ h = hashMask;
+ if (k instanceof String) {
+ return h ^ sun.misc.Hashing.stringHash32((String) k);
+ } else {
+ h ^= k.hashCode();
+ }
+ } else {
+ h = 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);
+ h ^= (h >>> 7) ^ (h >>> 4);
+
+ return h;
+ }
+
+ /**
* Returns index for hash code h.
*/
private static int indexFor(int h, int length) {
@@ -371,7 +465,7 @@
*/
public V get(Object key) {
Object k = maskNull(key);
- int h = HashMap.hash(k.hashCode());
+ int h = hash(k);
Entry[] tab = getTable();
int index = indexFor(h, tab.length);
Entry e = tab[index];
@@ -401,7 +495,7 @@
*/
Entry getEntry(Object key) {
Object k = maskNull(key);
- int h = HashMap.hash(k.hashCode());
+ int h = hash(k);
Entry[] tab = getTable();
int index = indexFor(h, tab.length);
Entry e = tab[index];
@@ -424,7 +518,7 @@
*/
public V put(K key, V value) {
Object k = maskNull(key);
- int h = HashMap.hash(k.hashCode());
+ int h = hash(k);
Entry[] tab = getTable();
int i = indexFor(h, tab.length);
@@ -468,7 +562,11 @@
}
Entry[] newTable = newTable(newCapacity);
- transfer(oldTable, newTable);
+ boolean oldAltHashing = useAltHashing;
+ useAltHashing |= sun.misc.VM.isBooted() &&
+ (newCapacity >= Holder.ALTERNATE_HASHING_THRESHOLD);
+ boolean rehash = oldAltHashing ^ useAltHashing;
+ transfer(oldTable, newTable, rehash);
table = newTable;
/*
@@ -480,13 +578,13 @@
threshold = (int)(newCapacity * loadFactor);
} else {
expungeStaleEntries();
- transfer(newTable, oldTable);
+ transfer(newTable, oldTable, false);
table = oldTable;
}
}
/** Transfers all entries from src to dest tables */
- private void transfer(Entry[] src, Entry[] dest) {
+ private void transfer(Entry[] src, Entry[] dest, boolean rehash) {
for (int j = 0; j < src.length; ++j) {
Entry e = src[j];
src[j] = null;
@@ -498,6 +596,9 @@
e.value = null; // " "
size--;
} else {
+ if(rehash) {
+ e.hash = hash(key);
+ }
int i = indexFor(e.hash, dest.length);
e.next = dest[i];
dest[i] = e;
@@ -566,7 +667,7 @@
*/
public V remove(Object key) {
Object k = maskNull(key);
- int h = HashMap.hash(k.hashCode());
+ int h = hash(k);
Entry[] tab = getTable();
int i = indexFor(h, tab.length);
Entry prev = tab[i];
@@ -597,7 +698,7 @@
Entry[] tab = getTable();
Map.Entry,?> entry = (Map.Entry,?>)o;
Object k = maskNull(entry.getKey());
- int h = HashMap.hash(k.hashCode());
+ int h = hash(k);
int i = indexFor(h, tab.length);
Entry prev = tab[i];
Entry e = prev;
@@ -679,7 +780,7 @@
*/
private static class Entry extends WeakReference