--- old/src/java.base/share/classes/java/lang/invoke/MemberName.java 2014-11-09 13:07:36.074588636 +0100 +++ new/src/java.base/share/classes/java/lang/invoke/MemberName.java 2014-11-09 13:07:35.964590549 +0100 @@ -75,22 +75,17 @@ * and those seven fields omit much of the information in Method. * @author jrose */ -@SuppressWarnings("rawtypes") //Comparable in next line -/*non-public*/ final class MemberName implements Member, Comparable, Cloneable { - private Class clazz; // class in which the method is defined - private String name; // may be null if not yet materialized - private Object type; // may be null if not yet materialized - private int flags; // modifier bits; see reflect.Modifier - private volatile MemberName next; // used for a linked list of MemberNames known to VM +/*non-public*/ final class MemberName implements Member, Cloneable { + private Class clazz; // class in which the method is defined + private String name; // may be null if not yet materialized + private Object type; // may be null if not yet materialized + private int flags; // modifier bits; see reflect.Modifier + private MemberName next; // used for a linked list of MemberNames known to VM //@Injected JVM_Method* vmtarget; //@Injected int vmindex; - private Object resolution; // if null, this guy is resolved + private Object resolution; // if null, this guy is resolved private static final JavaLangAccess jla = SharedSecrets.getJavaLangAccess(); - private static final Unsafe unsafe = Unsafe.getUnsafe(); - static void storeFence() { - unsafe.storeFence(); - } // bare-bones constructor; the JVM will fill it in MemberName() { } @@ -953,68 +948,6 @@ && Objects.equals(this.getType(), that.getType()); } - @Override - public int compareTo(Object o) { - MemberName that = (MemberName) o; - - /* First test equals. This make the ordering checks easier because we - * don't have to be precise and can use hash codes. - */ - if (equals(that)) { - return 0; - } - - int diff = Integer.compare(this.name.hashCode(), that.name.hashCode()); - if (diff != 0) { - return diff; - } - - diff = this.getReferenceKind() - that.getReferenceKind(); - if (diff != 0) { - return diff; - } - - diff = Integer.compare(this.getType().hashCode(), that.getType().hashCode()); - if (diff != 0) { - return diff; - } - - // Hashcodes apparently collided, try more detail. - diff = this.name.compareTo(that.name); - if (diff != 0) { - return diff; - } - - // The classes ought to all be equal anyway in the usual use of - // compareTo, so check these later, - // but before comparing getType.toString(). - diff = Integer.compare(this.clazz.hashCode(),that.clazz.hashCode()); - if (diff != 0) { - return diff; - } - - diff = this.clazz.getName().compareTo(that.clazz.getName()); - if (diff != 0) { - return diff; - } - - diff = this.getType().toString().compareTo(that.getType().toString()); - if (diff != 0) { - return diff; - } - - // Nothing left but classLoaders. - ClassLoader thisCl = this.getClassLoader(); - ClassLoader thatCl = that.getClassLoader(); - - if (thisCl == thatCl) return 0; - - diff = Integer.compare(thisCl == null ? 0 : thisCl.hashCode(), - thatCl == null ? 0 : thatCl.hashCode()); - - return diff; - } - /** Query whether this member name is resolved to a non-static, non-final method. */ public boolean hasReceiverTypeDispatch() { @@ -1358,15 +1291,19 @@ /** * Lazily create {@link ClassData}. */ - /* package */ static ClassData classData(Class klazz) { - if (jla.getClassData(klazz) == null) { - jla.casClassData(klazz, null, new ClassData()); + private static ClassData classData(Class klazz) { + ClassData classData = (ClassData) jla.getClassData(klazz); + if (classData == null) { + classData = new ClassData(); + if (!jla.casClassData(klazz, null, classData)) { + classData = (ClassData) jla.getClassData(klazz); + } } - return (ClassData) jla.getClassData(klazz); + return classData; } - /* package */ static MemberName internMemberName(Class klazz, MemberName memberName, int redefined_count) { - return classData(klazz).intern(klazz, memberName, redefined_count); + /* package */ static MemberName internMemberName(Class klazz, MemberName memberName, int redefinedCount) { + return classData(klazz).intern(klazz, memberName, redefinedCount); } /** @@ -1375,13 +1312,36 @@ private static class ClassData { /** * This needs to be a simple data structure because we need to access - * and update its elements from the JVM. Note that the Java side controls - * the allocation and order of elements in the array; the JVM modifies - * fields of those elements during class redefinition. + * and update its elements from the JVM. This is a 'tail' pointer + * to last element published to JVM. The chain of elements is + * traversed using {@link MemberName#next} links until {@code null} + * is reached. */ - private volatile MemberName[] elementData; private volatile MemberName publishedToVM; - private volatile int size; + + /** + * A linear-scan hash table used for interning. + * The length of the array is always a power of 2, + * greater than (1.5 * size) and less or equal to (3 * size) + */ + private volatile MemberName[] table; + private int size; + + /** + * The minimum capacity. + * The value 2 corresponds to an expected maximum size of 1, + * given a load factor of 2/3. MUST be a power of two. + */ + private static final int MINIMUM_CAPACITY = 2; + + /** + * The maximum capacity. + *

+ * In fact, the table can hold no more than MAXIMUM_CAPACITY-1 elements + * because it has to have at least one slot == null + * in order to avoid infinite loops in get() and putIfAbsent(). + */ + private static final int MAXIMUM_CAPACITY = 1 << 30; /** * Interns a member name in the member name table. @@ -1389,86 +1349,218 @@ * by checking for changes in the class redefinition count that occur * before an intern is complete. * - * @param klass class whose redefinition count is checked. - * @param memberName member name to be interned - * @param redefined_count the value of classRedefinedCount() observed before - * creation of the MemberName that is being interned. + * @param klass class whose redefinition count is checked. + * @param memberName member name to be interned + * @param redefinedCount the value of classRedefinedCount() observed before + * creation of the MemberName that is being interned. * @return null if a race occurred, otherwise the interned MemberName. */ - @SuppressWarnings({"unchecked","rawtypes"}) - public MemberName intern(Class klass, MemberName memberName, int redefined_count) { - if (elementData == null) { - synchronized (this) { - if (elementData == null) { - elementData = new MemberName[1]; - } + @SuppressWarnings({"unchecked", "rawtypes"}) + public MemberName intern(Class klass, MemberName memberName, int redefinedCount) { + + // check without holding lock + MemberName internedMemberName = get(memberName); + if (internedMemberName != null) { + return internedMemberName; + } + + // must hold lock now + synchronized (this) { + // This has to be a never published MemberName. + assert memberName.next == null; + + // First attempt publication to JVM, if that succeeds, + // then try to record in hash table. + memberName.next = publishedToVM; + publishedToVM = memberName; + if (redefinedCount != jla.getClassRedefinedCount(klass)) { + // Lost a race with JVM, back out publication and report failure. + publishedToVM = memberName.next; + // We can't set memberName.next to null here (without a write fence), + // because the write could bubble above the write to publishedToVM! + return null; } - } - synchronized (this) { // this == ClassData - final int index = Arrays.binarySearch(elementData, 0, size, memberName); - if (index >= 0) { - return elementData[index]; + + // Try to record in hash table. + internedMemberName = putIfAbsent(memberName); + if (internedMemberName != null) { + // Lost race with interning, back out JVM publication + // and return already interned element. + publishedToVM = memberName.next; + // We can't set memberName.next to null here (without a write fence), + // because the write could bubble above the write to publishedToVM! + return internedMemberName; } - // Not found, add carefully. - return add(klass, ~index, memberName, redefined_count); + + // Successfully recorded memberName, return it. + return memberName; } } /** - * Appends the specified element at the specified index. + * Lookup for an interned element. This method can be called without + * any external synchronization. * - * @param klass the klass for this ClassData. - * @param index index at which insertion should occur - * @param e element to be insert into this list - * @param redefined_count value of klass.classRedefinedCount() before e was created. - * @return null if insertion failed because of redefinition race, otherwise e. + * @param lookupElement the lookup object. + * @return interned element if there is one or null if there is none. */ - private MemberName add(Class klass, int index, MemberName e, int redefined_count) { - // First attempt publication to JVM, if that succeeds, - // then record internally. - e.next = publishedToVM; - publishedToVM = e; - storeFence(); - if (redefined_count != jla.getClassRedefinedCount(klass)) { - // Lost a race, back out publication and report failure. - publishedToVM = e.next; + private MemberName get(MemberName lookupElement) { + final MemberName[] tab = table; // volatile read + if (tab == null) { return null; } + int i = hash(lookupElement, tab.length); + while (true) { + MemberName element = getVolatile(tab, i); + if (element == null) { + return null; + } + if (element.equals(lookupElement)) { + return element; + } + i = nextKeyIndex(i, tab.length); + } + } + + /** + * Inserts new element if there is no interned element equal to it + * already present. This method must be called under exclusive lock. + * + * @param newElement new element to insert. + * @return null if new element was inserted or old element if + * there was one (and no new element was inserted) + */ + private MemberName putIfAbsent(MemberName newElement) { + if (newElement == null) throw new NullPointerException(); - int oldSize = size; - MemberName[] element_data = elementData; - if (oldSize + 1 > element_data.length ) { - grow(oldSize + 1); - element_data = elementData; + MemberName[] tab = table; // volatile read + + if (tab == null) { + // lazily create initial table + tab = new MemberName[MINIMUM_CAPACITY]; + // still virgin + tab[hash(newElement, tab.length)] = newElement; + // publish initial table + table = tab; + size = 1; + return null; } - if (oldSize > 0) { - for (int i = oldSize; i > index; i--) { - // pre: element_data[i] is duplicated at [i+1] - element_data[i] = element_data[i - 1]; - // post: element_data[i-1] is duplicated at [i] + while (true) { + + int i = hash(newElement, tab.length); + + for (MemberName element; + (element = tab[i]) != null; + i = nextKeyIndex(i, tab.length)) { + if (element.equals(newElement)) { + // return existing element + return element; + } + } + + final int newSize = size + 1; + + MemberName[] newTab; + if ((newTab = resize(tab, newSize)) != null) { // we needed to resize + // publish new table + table = tab = newTab; + // retry loop with new table + } else { // current tab.length is enough + // publish new element + setVolatile(tab, i, newElement); + size = newSize; + return null; } - // element_data[index] is duplicated at [index+1] } - element_data[index] = e; - size += 1; - return e; } + // utility methods + /** - * Increases the capacity to ensure that it can hold at least the - * number of elements specified by the minimum capacity argument. + * Re-sizes the table if necessary to hold given number of elements. * - * @param minCapacity the desired minimum capacity + * @return new table with elements copied to it if resizing was performed + * or null if no resizing is necessary because the table + * has enough room. */ - private void grow(int minCapacity) { - // overflow-conscious code - int oldCapacity = elementData.length; - int newCapacity = oldCapacity + (oldCapacity >> 1); - if (newCapacity - minCapacity < 0) - newCapacity = minCapacity; - // minCapacity is usually close to size, so this is a win: - elementData = Arrays.copyOf(elementData, newCapacity); + private static MemberName[] resize(MemberName[] oldTable, int newSize) { + int oldLength = oldTable.length; + int newLength = oldLength; + // length should always be >= 3 * size / 2. + while (newLength < newSize + (newSize >> 1) && + newLength < MAXIMUM_CAPACITY) { + // next power of 2 + newLength <<= 1; + } + if (oldLength == newLength) { // no resizing needed + if (newSize == MAXIMUM_CAPACITY) + throw new IllegalStateException("Capacity exhausted."); + // current length is enough + return null; + } + + MemberName[] newTable = new MemberName[newLength]; + + // copy elements to new table + for (MemberName element : oldTable) { + if (element != null) { + int i = hash(element, newLength); + while (newTable[i] != null) + i = nextKeyIndex(i, newLength); + newTable[i] = element; + } + } + + return newTable; + } + + /** + * Returns index for Object x. + */ + private static int hash(Object x, int length) { + int h = x.hashCode(); + return (h ^ (h >>> 16)) & (length - 1); + } + + /** + * Circularly traverses table of size length (which is a power of 2). + */ + private static int nextKeyIndex(int i, int length) { + return (i + 1) & (length - 1); + } + + // Unsafe machinery + private static final Unsafe UNSAFE = Unsafe.getUnsafe(); + private static final long MN_ARRAY_BASE; + private static final int MN_ARRAY_INDEX_SHIFT; + + static { + try { + MN_ARRAY_BASE = UNSAFE.arrayBaseOffset( + MemberName[].class + ); + int scale = UNSAFE.arrayIndexScale(MemberName[].class); + if ((scale & (scale - 1)) != 0) + throw new Error("MemberName[] index scale not a power of two"); + MN_ARRAY_INDEX_SHIFT = 31 - Integer.numberOfLeadingZeros(scale); + } catch (Exception e) { + throw new Error(e); + } + } + + private static MemberName getVolatile(MemberName[] tab, int i) { + return (MemberName) UNSAFE.getObjectVolatile(tab, arrayOffset(tab, i)); + } + + private static void setVolatile(MemberName[] tab, int i, MemberName element) { + UNSAFE.putObjectVolatile(tab, arrayOffset(tab, i), element); + } + + private static long arrayOffset(MemberName[] tab, int i) { + if (i < 0 || i >= tab.length) + throw new ArrayIndexOutOfBoundsException(i); + return ((long) i << MN_ARRAY_INDEX_SHIFT) + MN_ARRAY_BASE; } } }