7127687: MethodType leaks memory due to interning
Replace internTable with a weak-reference version. Reviewed-by: sundar, forax, brutisso
This commit is contained in:
parent
c04c841a70
commit
8bc65af302
@ -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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -26,9 +26,10 @@
|
|||||||
package java.lang.invoke;
|
package java.lang.invoke;
|
||||||
|
|
||||||
import sun.invoke.util.Wrapper;
|
import sun.invoke.util.Wrapper;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.lang.ref.ReferenceQueue;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import sun.invoke.util.BytecodeDescriptor;
|
import sun.invoke.util.BytecodeDescriptor;
|
||||||
import static java.lang.invoke.MethodHandleStatics.*;
|
import static java.lang.invoke.MethodHandleStatics.*;
|
||||||
@ -135,8 +136,7 @@ class MethodType implements java.io.Serializable {
|
|||||||
return new IndexOutOfBoundsException(num.toString());
|
return new IndexOutOfBoundsException(num.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
static final HashMap<MethodType,MethodType> internTable
|
static final WeakInternSet internTable = new WeakInternSet();
|
||||||
= new HashMap<MethodType, MethodType>();
|
|
||||||
|
|
||||||
static final Class<?>[] NO_PTYPES = {};
|
static final Class<?>[] NO_PTYPES = {};
|
||||||
|
|
||||||
@ -239,11 +239,9 @@ class MethodType implements java.io.Serializable {
|
|||||||
}
|
}
|
||||||
MethodType mt1 = new MethodType(rtype, ptypes);
|
MethodType mt1 = new MethodType(rtype, ptypes);
|
||||||
MethodType mt0;
|
MethodType mt0;
|
||||||
synchronized (internTable) {
|
mt0 = internTable.get(mt1);
|
||||||
mt0 = internTable.get(mt1);
|
if (mt0 != null)
|
||||||
if (mt0 != null)
|
return mt0;
|
||||||
return mt0;
|
|
||||||
}
|
|
||||||
if (!trusted)
|
if (!trusted)
|
||||||
// defensively copy the array passed in by the user
|
// defensively copy the array passed in by the user
|
||||||
mt1 = new MethodType(rtype, ptypes.clone());
|
mt1 = new MethodType(rtype, ptypes.clone());
|
||||||
@ -254,15 +252,8 @@ class MethodType implements java.io.Serializable {
|
|||||||
// This is a principal (erased) type; show it to the JVM.
|
// This is a principal (erased) type; show it to the JVM.
|
||||||
MethodHandleNatives.init(mt1);
|
MethodHandleNatives.init(mt1);
|
||||||
}
|
}
|
||||||
synchronized (internTable) {
|
return internTable.add(mt1);
|
||||||
mt0 = internTable.get(mt1);
|
|
||||||
if (mt0 != null)
|
|
||||||
return mt0;
|
|
||||||
internTable.put(mt1, mt1);
|
|
||||||
}
|
|
||||||
return mt1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final MethodType[] objectOnlyTypes = new MethodType[20];
|
private static final MethodType[] objectOnlyTypes = new MethodType[20];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -919,4 +910,269 @@ s.writeObject(this.parameterArray());
|
|||||||
// Verify all operands, and make sure ptypes is unshared:
|
// Verify all operands, and make sure ptypes is unshared:
|
||||||
return methodType(rtype, ptypes);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user