jdk/src/share/classes/java/lang/invoke/MethodType.java

Print this page

        

*** 25,38 **** --- 25,41 ---- package java.lang.invoke; import sun.invoke.util.Wrapper; import java.lang.ref.WeakReference; + import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.util.Arrays; import java.util.Collections; import java.util.List; + import java.util.concurrent.ConcurrentMap; + import java.util.concurrent.ConcurrentHashMap; import sun.invoke.util.BytecodeDescriptor; import static java.lang.invoke.MethodHandleStatics.*; import sun.invoke.util.VerifyType; /**
*** 169,179 **** private static IndexOutOfBoundsException newIndexOutOfBoundsException(Object num) { if (num instanceof Integer) num = "bad index: "+num; return new IndexOutOfBoundsException(num.toString()); } ! static final WeakInternSet internTable = new WeakInternSet(); static final Class<?>[] NO_PTYPES = {}; /** * Finds or creates an instance of the given method type. --- 172,182 ---- private static IndexOutOfBoundsException newIndexOutOfBoundsException(Object num) { if (num instanceof Integer) num = "bad index: "+num; return new IndexOutOfBoundsException(num.toString()); } ! static final ConcurrentWeakCache<MethodType> internTable = new ConcurrentWeakCache<>(); static final Class<?>[] NO_PTYPES = {}; /** * Finds or creates an instance of the given method type.
*** 1011,1279 **** // Verify all operands, and make sure ptypes is unshared: return methodType(rtype, ptypes); } /** ! * Weak intern set based on implementation of the <tt>HashSet</tt> and ! * <tt>WeakHashMap</tt>, with <em>weak values</em>. Note: <tt>null</tt> ! * values will yield <tt>NullPointerException</tt> ! * 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<Object> queue = new ReferenceQueue<>(); ! ! private Entry[] newTable(int n) { ! return new Entry[n]; } /** ! * Constructs a new, empty <tt>WeakInternSet</tt> 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 <tt>true</tt> 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. ! * ! * <p>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 <tt>null</tt> ! */ ! 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 <tt>value</tt>, or ! * <tt>value</tt> 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<MethodType> { ! final int hash; ! Entry next; ! ! /** ! * Creates new entry. ! */ ! Entry(MethodType key, ! ReferenceQueue<Object> queue, ! int hash, Entry next) { ! super(key, queue); ! this.hash = hash; ! this.next = next; } } } } --- 1014,1121 ---- // Verify all operands, and make sure ptypes is unshared: return methodType(rtype, ptypes); } /** ! * Simple implementation of weak concurrent cache. * ! * @param <T> cached type */ ! private static class ConcurrentWeakCache<T> { ! private final ConcurrentMap<WeakEntry<T>, WeakEntry<T>> map; ! private final ReferenceQueue<T> stale; ! public ConcurrentWeakCache() { ! this.map = new ConcurrentHashMap<>(); ! this.stale = new ReferenceQueue<>(); } /** ! * Get the cached element. ! * This method can spuriously return null ! * (e.g. under the race against the reference processing). ! * ! * @param elem element to look up ! * @return the cached element */ ! public T get(T elem) { ! if (elem == null) throw new NullPointerException(); ! expungeStaleElements(); ! ! WeakEntry<T> value = map.get(new WeakEntry<>(elem)); ! if (value != null) { ! T res = value.get(); ! if (res != null) { ! return res; } } ! return null; } /** ! * Adds the element to cache. ! * Always returns non-null element, matching the one in the cache. ! * Under the race against another add(), it can return <i>different</i> ! * element, if other thread beats us to adding to the cache. ! * ! * @param elem element to add ! * @return element that was actually added */ ! public T add(T elem) { ! if (elem == null) throw new NullPointerException(); ! // Playing double race here, and so spinloop is required. ! // First race is with two concurrent updaters. ! // Second race is with GC purging weak ref under our feet. ! // Hopefully, we almost always end up with a single pass. ! T interned; ! do { ! expungeStaleElements(); ! WeakEntry<T> e = new WeakEntry<>(elem, stale); ! WeakEntry<T> exist = map.putIfAbsent(e, e); ! WeakEntry<T> winner = (exist != null ? exist : e); ! interned = winner.get(); ! } while (interned == null); ! return interned; } + + private void expungeStaleElements() { + Reference<? extends T> reference; + while ((reference = stale.poll()) != null) { + map.remove((WeakEntry)reference); } } ! private static class WeakEntry<T> extends WeakReference<T> { ! public final int hashcode; ! public WeakEntry(T key, ReferenceQueue<T> queue) { ! super(key, queue); ! hashcode = key.hashCode(); } ! public WeakEntry(T key) { ! super(key); ! hashcode = key.hashCode(); } ! @Override ! public boolean equals(Object obj) { ! if ((obj != null) && (obj instanceof WeakEntry)) { ! T that = ((WeakEntry<T>) obj).get(); ! T mine = get(); ! return (that == null || mine == null) ? (this == obj) : mine.equals(that); } + return false; } ! @Override ! public int hashCode() { ! return hashcode; } + } } + }