7127687: MethodType leaks memory due to interning
authorjrose
Thu, 12 Jul 2012 00:12:52 -0700
changeset 13422 72b63ee33f57
parent 13421 d20670384420
child 13423 17843fff200d
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
jdk/src/share/classes/java/lang/invoke/MethodType.java
--- a/jdk/src/share/classes/java/lang/invoke/MethodType.java	Thu Jul 12 00:12:28 2012 -0700
+++ b/jdk/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<MethodType,MethodType> internTable
-            = new HashMap<MethodType, MethodType>();
+    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 <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;
+            }
+        }
+    }
 }