# HG changeset patch # User jrose # Date 1342077172 25200 # Node ID 78f1f4e4e9c75d389f0a6a720346f05637068499 # Parent 556141c6326c2c76a8ec15bda65a7c1e6a844ddb 7127687: MethodType leaks memory due to interning Summary: Replace internTable with a weak-reference version. Reviewed-by: sundar, forax, brutisso Contributed-by: james.laskey@oracle.com diff -r 556141c6326c -r 78f1f4e4e9c7 src/share/classes/java/lang/invoke/MethodType.java --- a/src/share/classes/java/lang/invoke/MethodType.java Thu Jul 12 00:12:28 2012 -0700 +++ b/src/share/classes/java/lang/invoke/MethodType.java Thu Jul 12 00:12:52 2012 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,9 +26,10 @@ package java.lang.invoke; import sun.invoke.util.Wrapper; +import java.lang.ref.WeakReference; +import java.lang.ref.ReferenceQueue; import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; import java.util.List; import sun.invoke.util.BytecodeDescriptor; import static java.lang.invoke.MethodHandleStatics.*; @@ -135,8 +136,7 @@ return new IndexOutOfBoundsException(num.toString()); } - static final HashMap internTable - = new HashMap(); + static final WeakInternSet internTable = new WeakInternSet(); static final Class[] NO_PTYPES = {}; @@ -239,11 +239,9 @@ } MethodType mt1 = new MethodType(rtype, ptypes); MethodType mt0; - synchronized (internTable) { - mt0 = internTable.get(mt1); - if (mt0 != null) - return mt0; - } + mt0 = internTable.get(mt1); + if (mt0 != null) + return mt0; if (!trusted) // defensively copy the array passed in by the user mt1 = new MethodType(rtype, ptypes.clone()); @@ -254,15 +252,8 @@ // This is a principal (erased) type; show it to the JVM. MethodHandleNatives.init(mt1); } - synchronized (internTable) { - mt0 = internTable.get(mt1); - if (mt0 != null) - return mt0; - internTable.put(mt1, mt1); - } - return mt1; + return internTable.add(mt1); } - private static final MethodType[] objectOnlyTypes = new MethodType[20]; /** @@ -919,4 +910,269 @@ // Verify all operands, and make sure ptypes is unshared: return methodType(rtype, ptypes); } + + /** + * Weak intern set based on implementation of the HashSet and + * WeakHashMap, with weak values. Note: null + * values will yield NullPointerException + * Refer to implementation of WeakInternSet for details. + * + * @see java.util.HashMap + * @see java.util.HashSet + * @see java.util.WeakHashMap + * @see java.lang.ref.WeakReference + */ + private static class WeakInternSet { + // The default initial capacity -- MUST be a power of two. + private static final int DEFAULT_INITIAL_CAPACITY = 16; + + // The maximum capacity, used if a higher value is implicitly specified + // by either of the constructors with arguments. + // MUST be a power of two <= 1<<30. + private static final int MAXIMUM_CAPACITY = 1 << 30; + + // The load factor used when none specified in constructor. + private static final float DEFAULT_LOAD_FACTOR = 0.75f; + + // The table, resized as necessary. Length MUST Always be a power of two. + private Entry[] table; + + // The number of entries contained in this set. + private int size; + + // The next size value at which to resize (capacity * load factor). + private int threshold; + + // The load factor for the hash table. + private final float loadFactor; + + // Reference queue for cleared WeakEntries + private final ReferenceQueue queue = new ReferenceQueue<>(); + + private Entry[] newTable(int n) { + return new Entry[n]; + } + + /** + * Constructs a new, empty WeakInternSet with the default initial + * capacity (16) and load factor (0.75). + */ + WeakInternSet() { + this.loadFactor = DEFAULT_LOAD_FACTOR; + threshold = DEFAULT_INITIAL_CAPACITY; + table = newTable(DEFAULT_INITIAL_CAPACITY); + } + + /** + * Applies a supplemental hash function to a given hashCode, which + * defends against poor quality hash functions. This is critical + * because hashing uses power-of-two length hash tables, that + * otherwise encounter collisions for hashCodes that do not differ + * in lower bits. + * @param h preliminary hash code value + * @return supplemental hash code value + */ + private static int hash(int h) { + // 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); + } + + /** + * Checks for equality of non-null reference x and possibly-null y. By + * default uses Object.equals. + * @param x first object to compare + * @param y second object to compare + * @return true if objects are equal + */ + private static boolean eq(Object x, Object y) { + return x == y || x.equals(y); + } + + /** + * Returns index for hash code h. + * @param h raw hash code + * @param length length of table (power of 2) + * @return index in table + */ + private static int indexFor(int h, int length) { + return h & (length-1); + } + + /** + * Expunges stale entries from the table. + */ + private void expungeStaleEntries() { + for (Object x; (x = queue.poll()) != null; ) { + synchronized (queue) { + Entry entry = (Entry) x; + int i = indexFor(entry.hash, table.length); + Entry prev = table[i]; + Entry p = prev; + while (p != null) { + Entry next = p.next; + if (p == entry) { + if (prev == entry) + table[i] = next; + else + prev.next = next; + entry.next = null; + size--; + break; + } + prev = p; + p = next; + } + } + } + } + + /** + * Returns the table after first expunging stale entries. + * @return an expunged hash table + */ + private Entry[] getTable() { + expungeStaleEntries(); + return table; + } + + /** + * Returns the entry to which the specified value is mapped, + * or {@code null} if this set contains no entry for the value. + * + *

More formally, if this set contains an entry for value + * {@code entry} to a value {@code value} such that + * {@code entry.equals(value)}, then this method returns {@code entry}; + * otherwise it returns {@code null}. + * + * @param value value to search for in set + * @return interned value if in set, otherwise null + */ + synchronized MethodType get(MethodType value) { + int h = hash(value.hashCode()); + Entry[] tab = getTable(); + int index = indexFor(h, tab.length); + Entry e = tab[index]; + MethodType g; + while (e != null) { + if (e.hash == h && eq(value, g = e.get())) + return g; + e = e.next; + } + return null; + } + + /** + * Attempts to add the specified value to the set and returns same value. + * If the set previously contained an entry for this value, the old + * value is left untouched and returned as the result. + * + * @param value value to be added + * @return the previous entry associated with value, or + * value if there was no previous entry found + */ + synchronized MethodType add(MethodType value) { + int h = hash(value.hashCode()); + Entry[] tab = getTable(); + int i = indexFor(h, tab.length); + MethodType g; + for (Entry e = tab[i]; e != null; e = e.next) { + if (h == e.hash && eq(value, g = e.get())) { + return g; + } + } + Entry e = tab[i]; + tab[i] = new Entry(value, queue, h, e); + if (++size >= threshold) + resize(tab.length * 2); + return value; + } + + /** + * Rehashes the contents of this set into a new array with a + * larger capacity. This method is called automatically when the + * number of keys in this set reaches its threshold. + * + * If current capacity is MAXIMUM_CAPACITY, this method does not + * resize the set, but sets threshold to Integer.MAX_VALUE. + * This has the effect of preventing future calls. + * + * @param newCapacity the new capacity, MUST be a power of two; + * must be greater than current capacity unless current + * capacity is MAXIMUM_CAPACITY (in which case value + * is irrelevant) + */ + private void resize(int newCapacity) { + Entry[] oldTable = getTable(); + int oldCapacity = oldTable.length; + if (oldCapacity == MAXIMUM_CAPACITY) { + threshold = Integer.MAX_VALUE; + return; + } + + Entry[] newTable = newTable(newCapacity); + transfer(oldTable, newTable); + table = newTable; + + /* + * If ignoring null elements and processing ref queue caused massive + * shrinkage, then restore old table. This should be rare, but avoids + * unbounded expansion of garbage-filled tables. + */ + if (size >= threshold / 2) { + threshold = (int)(newCapacity * loadFactor); + } else { + expungeStaleEntries(); + transfer(newTable, oldTable); + table = oldTable; + } + } + + /** + * Transfers all entries from src to dest tables + * @param src original table + * @param dest new table + */ + private void transfer(Entry[] src, Entry[] dest) { + for (int j = 0; j < src.length; ++j) { + Entry e = src[j]; + src[j] = null; + while (e != null) { + Entry next = e.next; + MethodType key = e.get(); + if (key == null) { + e.next = null; // Help GC + size--; + } else { + int i = indexFor(e.hash, dest.length); + e.next = dest[i]; + dest[i] = e; + } + e = next; + } + } + } + + /** + * The entries in this hash table extend WeakReference, using its main ref + * field as the key. + */ + private static class Entry extends WeakReference { + final int hash; + Entry next; + + /** + * Creates new entry. + */ + Entry(MethodType key, + ReferenceQueue queue, + int hash, Entry next) { + super(key, queue); + this.hash = hash; + this.next = next; + } + } + } }