src/java.base/share/classes/java/lang/invoke/MemberName.java
Print this page
*** 73,98 ****
* allows invocation. A MemberName is much lighter than a Method,
* since it contains about 7 fields to the 16 of Method (plus its sub-arrays),
* 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
//@Injected JVM_Method* vmtarget;
//@Injected int vmindex;
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() { }
/** Create a name for the given class. The resulting name will be in a resolved state. */
--- 73,93 ----
* allows invocation. A MemberName is much lighter than a Method,
* since it contains about 7 fields to the 16 of Method (plus its sub-arrays),
* and those seven fields omit much of the information in Method.
* @author jrose
*/
! /*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 static final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
// bare-bones constructor; the JVM will fill it in
MemberName() { }
/** Create a name for the given class. The resulting name will be in a resolved state. */
*** 951,1022 ****
&& this.getReferenceKind() == that.getReferenceKind()
&& Objects.equals(this.name, that.name)
&& 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() {
return MethodHandleNatives.refKindDoesDispatch(getReferenceKind());
}
--- 946,955 ----
*** 1356,1474 ****
/**
* Lazily create {@link ClassData}.
*/
! /* package */ static ClassData classData(Class<?> klazz) {
! if (jla.getClassData(klazz) == null) {
! jla.casClassData(klazz, null, new ClassData());
}
! return (ClassData) jla.getClassData(klazz);
}
! /* package */ static MemberName internMemberName(Class<?> klazz, MemberName memberName, int redefined_count) {
! return classData(klazz).intern(klazz, memberName, redefined_count);
}
/**
* ClassData
*/
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.
*/
- private volatile MemberName[] elementData;
private volatile MemberName publishedToVM;
! private volatile int size;
/**
* Interns a member name in the member name table.
* Returns null if a race with the jvm occurred. Races are detected
* 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.
* @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];
}
}
}
! synchronized (this) { // this == ClassData
! final int index = Arrays.binarySearch(elementData, 0, size, memberName);
! if (index >= 0) {
! return elementData[index];
}
! // Not found, add carefully.
! return add(klass, ~index, memberName, redefined_count);
}
}
/**
! * Appends the specified element at the specified index.
*
! * @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.
! */
! 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;
return null;
}
! int oldSize = size;
! MemberName[] element_data = elementData;
! if (oldSize + 1 > element_data.length ) {
! grow(oldSize + 1);
! element_data = elementData;
}
! 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]
}
- // element_data[index] is duplicated at [index+1]
}
- element_data[index] = e;
- size += 1;
- return e;
}
/**
! * Increases the capacity to ensure that it can hold at least the
! * number of elements specified by the minimum capacity argument.
*
! * @param minCapacity the desired minimum capacity
*/
! 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);
}
}
}
--- 1289,1566 ----
/**
* Lazily create {@link 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;
}
! /* package */ static MemberName internMemberName(Class<?> klazz, MemberName memberName, int redefinedCount) {
! return classData(klazz).intern(klazz, memberName, redefinedCount);
}
/**
* ClassData
*/
private static class ClassData {
/**
* This needs to be a simple data structure because we need to access
! * 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 publishedToVM;
!
! /**
! * 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.
! * <p/>
! * 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.
* Returns null if a race with the jvm occurred. Races are detected
* 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 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 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;
! }
!
! // 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;
}
+
+ // Successfully recorded memberName, return it.
+ return memberName;
}
}
!
! /**
! * Lookup for an interned element. This method can be called without
! * any external synchronization.
! *
! * @param lookupElement the lookup object.
! * @return interned element if there is one or null if there is none.
! */
! 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();
!
! 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;
}
! 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;
}
}
}
+ // utility methods
+
/**
! * Re-sizes the table if necessary to hold given number of elements.
*
! * @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 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;
}
}
}