8011940: java.lang.Class.getAnnotations() always enters synchronized method
Reviewed-by: jfranck, chegar, psandoz, shade
--- a/jdk/src/share/classes/java/lang/Class.java Thu Sep 19 17:04:45 2013 +0400
+++ b/jdk/src/share/classes/java/lang/Class.java Thu Sep 19 16:14:13 2013 +0200
@@ -48,6 +48,7 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Set;
import java.util.Map;
@@ -2370,11 +2371,14 @@
private static final long reflectionDataOffset;
// offset of Class.annotationType instance field
private static final long annotationTypeOffset;
+ // offset of Class.annotationData instance field
+ private static final long annotationDataOffset;
static {
Field[] fields = Class.class.getDeclaredFields0(false); // bypass caches
reflectionDataOffset = objectFieldOffset(fields, "reflectionData");
annotationTypeOffset = objectFieldOffset(fields, "annotationType");
+ annotationDataOffset = objectFieldOffset(fields, "annotationData");
}
private static long objectFieldOffset(Field[] fields, String fieldName) {
@@ -2396,6 +2400,12 @@
AnnotationType newType) {
return unsafe.compareAndSwapObject(clazz, annotationTypeOffset, oldType, newType);
}
+
+ static <T> boolean casAnnotationData(Class<?> clazz,
+ AnnotationData oldData,
+ AnnotationData newData) {
+ return unsafe.compareAndSwapObject(clazz, annotationDataOffset, oldData, newData);
+ }
}
/**
@@ -2406,7 +2416,7 @@
private static boolean useCaches = true;
// reflection data that might get invalidated when JVM TI RedefineClasses() is called
- static class ReflectionData<T> {
+ private static class ReflectionData<T> {
volatile Field[] declaredFields;
volatile Field[] publicFields;
volatile Method[] declaredMethods;
@@ -3253,8 +3263,7 @@
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
Objects.requireNonNull(annotationClass);
- initAnnotationsIfNecessary();
- return (A) annotations.get(annotationClass);
+ return (A) annotationData().annotations.get(annotationClass);
}
/**
@@ -3275,16 +3284,14 @@
public <A extends Annotation> A[] getAnnotationsByType(Class<A> annotationClass) {
Objects.requireNonNull(annotationClass);
- initAnnotationsIfNecessary();
- return AnnotationSupport.getMultipleAnnotations(annotations, annotationClass);
+ return AnnotationSupport.getMultipleAnnotations(annotationData().annotations, annotationClass);
}
/**
* @since 1.5
*/
public Annotation[] getAnnotations() {
- initAnnotationsIfNecessary();
- return AnnotationParser.toArray(annotations);
+ return AnnotationParser.toArray(annotationData().annotations);
}
/**
@@ -3296,8 +3303,7 @@
public <A extends Annotation> A getDeclaredAnnotation(Class<A> annotationClass) {
Objects.requireNonNull(annotationClass);
- initAnnotationsIfNecessary();
- return (A) declaredAnnotations.get(annotationClass);
+ return (A) annotationData().declaredAnnotations.get(annotationClass);
}
/**
@@ -3308,52 +3314,85 @@
public <A extends Annotation> A[] getDeclaredAnnotationsByType(Class<A> annotationClass) {
Objects.requireNonNull(annotationClass);
- initAnnotationsIfNecessary();
- return AnnotationSupport.getMultipleAnnotations(declaredAnnotations, annotationClass);
+ return AnnotationSupport.getMultipleAnnotations(annotationData().declaredAnnotations, annotationClass);
}
/**
* @since 1.5
*/
public Annotation[] getDeclaredAnnotations() {
- initAnnotationsIfNecessary();
- return AnnotationParser.toArray(declaredAnnotations);
+ return AnnotationParser.toArray(annotationData().declaredAnnotations);
}
- // Annotations cache
- private transient Map<Class<? extends Annotation>, Annotation> annotations;
- private transient Map<Class<? extends Annotation>, Annotation> declaredAnnotations;
- // Value of classRedefinedCount when we last cleared the cached annotations and declaredAnnotations fields
- private transient int lastAnnotationsRedefinedCount = 0;
+ // annotation data that might get invalidated when JVM TI RedefineClasses() is called
+ private static class AnnotationData {
+ final Map<Class<? extends Annotation>, Annotation> annotations;
+ final Map<Class<? extends Annotation>, Annotation> declaredAnnotations;
- // Clears cached values that might possibly have been obsoleted by
- // a class redefinition.
- private void clearAnnotationCachesOnClassRedefinition() {
- if (lastAnnotationsRedefinedCount != classRedefinedCount) {
- annotations = declaredAnnotations = null;
- lastAnnotationsRedefinedCount = classRedefinedCount;
+ // Value of classRedefinedCount when we created this AnnotationData instance
+ final int redefinedCount;
+
+ AnnotationData(Map<Class<? extends Annotation>, Annotation> annotations,
+ Map<Class<? extends Annotation>, Annotation> declaredAnnotations,
+ int redefinedCount) {
+ this.annotations = annotations;
+ this.declaredAnnotations = declaredAnnotations;
+ this.redefinedCount = redefinedCount;
}
}
- private synchronized void initAnnotationsIfNecessary() {
- clearAnnotationCachesOnClassRedefinition();
- if (annotations != null)
- return;
- declaredAnnotations = AnnotationParser.parseAnnotations(
- getRawAnnotations(), getConstantPool(), this);
+ // Annotations cache
+ @SuppressWarnings("UnusedDeclaration")
+ private volatile transient AnnotationData annotationData;
+
+ private AnnotationData annotationData() {
+ while (true) { // retry loop
+ AnnotationData annotationData = this.annotationData;
+ int classRedefinedCount = this.classRedefinedCount;
+ if (annotationData != null &&
+ annotationData.redefinedCount == classRedefinedCount) {
+ return annotationData;
+ }
+ // null or stale annotationData -> optimistically create new instance
+ AnnotationData newAnnotationData = createAnnotationData(classRedefinedCount);
+ // try to install it
+ if (Atomic.casAnnotationData(this, annotationData, newAnnotationData)) {
+ // successfully installed new AnnotationData
+ return newAnnotationData;
+ }
+ }
+ }
+
+ private AnnotationData createAnnotationData(int classRedefinedCount) {
+ Map<Class<? extends Annotation>, Annotation> declaredAnnotations =
+ AnnotationParser.parseAnnotations(getRawAnnotations(), getConstantPool(), this);
Class<?> superClass = getSuperclass();
- if (superClass == null) {
+ Map<Class<? extends Annotation>, Annotation> annotations = null;
+ if (superClass != null) {
+ Map<Class<? extends Annotation>, Annotation> superAnnotations =
+ superClass.annotationData().annotations;
+ for (Map.Entry<Class<? extends Annotation>, Annotation> e : superAnnotations.entrySet()) {
+ Class<? extends Annotation> annotationClass = e.getKey();
+ if (AnnotationType.getInstance(annotationClass).isInherited()) {
+ if (annotations == null) { // lazy construction
+ annotations = new LinkedHashMap<>((Math.max(
+ declaredAnnotations.size(),
+ Math.min(12, declaredAnnotations.size() + superAnnotations.size())
+ ) * 4 + 2) / 3
+ );
+ }
+ annotations.put(annotationClass, e.getValue());
+ }
+ }
+ }
+ if (annotations == null) {
+ // no inherited annotations -> share the Map with declaredAnnotations
annotations = declaredAnnotations;
} else {
- annotations = new HashMap<>();
- superClass.initAnnotationsIfNecessary();
- for (Map.Entry<Class<? extends Annotation>, Annotation> e : superClass.annotations.entrySet()) {
- Class<? extends Annotation> annotationClass = e.getKey();
- if (AnnotationType.getInstance(annotationClass).isInherited())
- annotations.put(annotationClass, e.getValue());
- }
+ // at least one inherited annotation -> declared may override inherited
annotations.putAll(declaredAnnotations);
}
+ return new AnnotationData(annotations, declaredAnnotations, classRedefinedCount);
}
// Annotation types cache their internal (AnnotationType) form
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/annotation/AnnotationsInheritanceOrderRedefinitionTest.java Thu Sep 19 16:14:13 2013 +0200
@@ -0,0 +1,210 @@
+/*
+ * Copyright (c) 2013, 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
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8011940
+ * @summary Test inheritance, order and class redefinition behaviour of RUNTIME
+ * class annotations
+ * @author plevart
+ */
+
+import sun.reflect.annotation.AnnotationParser;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.StringJoiner;
+
+public class AnnotationsInheritanceOrderRedefinitionTest {
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Inherited
+ @interface Ann1 {
+ String value();
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Inherited
+ @interface Ann2 {
+ String value();
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Inherited
+ @interface Ann3 {
+ String value();
+ }
+
+ @Ann1("A")
+ @Ann2("A")
+ static class A {}
+
+ @Ann3("B")
+ static class B extends A {}
+
+ @Ann1("C")
+ @Ann3("C")
+ static class C extends B {}
+
+ public static void main(String[] args) {
+
+ StringBuilder msgs = new StringBuilder();
+ boolean ok = true;
+
+ ok &= annotationsEqual(msgs, A.class, true,
+ ann(Ann1.class, "A"), ann(Ann2.class, "A"));
+ ok &= annotationsEqual(msgs, A.class, false,
+ ann(Ann1.class, "A"), ann(Ann2.class, "A"));
+ ok &= annotationsEqual(msgs, B.class, true,
+ ann(Ann3.class, "B"));
+ ok &= annotationsEqual(msgs, B.class, false,
+ ann(Ann1.class, "A"), ann(Ann2.class, "A"), ann(Ann3.class, "B"));
+ ok &= annotationsEqual(msgs, C.class, true,
+ ann(Ann1.class, "C"), ann(Ann3.class, "C"));
+ ok &= annotationsEqual(msgs, C.class, false,
+ ann(Ann1.class, "C"), ann(Ann2.class, "A"), ann(Ann3.class, "C"));
+
+ Annotation[] declaredAnnotatiosA = A.class.getDeclaredAnnotations();
+ Annotation[] annotationsA = A.class.getAnnotations();
+ Annotation[] declaredAnnotatiosB = B.class.getDeclaredAnnotations();
+ Annotation[] annotationsB = B.class.getAnnotations();
+ Annotation[] declaredAnnotatiosC = C.class.getDeclaredAnnotations();
+ Annotation[] annotationsC = C.class.getAnnotations();
+
+ incrementClassRedefinedCount(A.class);
+ incrementClassRedefinedCount(B.class);
+ incrementClassRedefinedCount(C.class);
+
+ ok &= annotationsEqualButNotSame(msgs, A.class, true, declaredAnnotatiosA);
+ ok &= annotationsEqualButNotSame(msgs, A.class, false, annotationsA);
+ ok &= annotationsEqualButNotSame(msgs, B.class, true, declaredAnnotatiosB);
+ ok &= annotationsEqualButNotSame(msgs, B.class, false, annotationsB);
+ ok &= annotationsEqualButNotSame(msgs, C.class, true, declaredAnnotatiosC);
+ ok &= annotationsEqualButNotSame(msgs, C.class, false, annotationsC);
+
+ if (!ok) {
+ throw new RuntimeException("test failure\n" + msgs);
+ }
+ }
+
+ // utility methods
+
+ private static boolean annotationsEqualButNotSame(StringBuilder msgs,
+ Class<?> declaringClass, boolean declaredOnly, Annotation[] oldAnns) {
+ if (!annotationsEqual(msgs, declaringClass, declaredOnly, oldAnns)) {
+ return false;
+ }
+ Annotation[] anns = declaredOnly
+ ? declaringClass.getDeclaredAnnotations()
+ : declaringClass.getAnnotations();
+ List<Annotation> sameAnns = new ArrayList<>();
+ for (int i = 0; i < anns.length; i++) {
+ if (anns[i] == oldAnns[i]) {
+ sameAnns.add(anns[i]);
+ }
+ }
+ if (!sameAnns.isEmpty()) {
+ msgs.append(declaredOnly ? "declared " : "").append("annotations for ")
+ .append(declaringClass.getSimpleName())
+ .append(" not re-parsed after class redefinition: ")
+ .append(toSimpleString(sameAnns)).append("\n");
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ private static boolean annotationsEqual(StringBuilder msgs,
+ Class<?> declaringClass, boolean declaredOnly, Annotation... expectedAnns) {
+ Annotation[] anns = declaredOnly
+ ? declaringClass.getDeclaredAnnotations()
+ : declaringClass.getAnnotations();
+ if (!Arrays.equals(anns, expectedAnns)) {
+ msgs.append(declaredOnly ? "declared " : "").append("annotations for ")
+ .append(declaringClass.getSimpleName()).append(" are: ")
+ .append(toSimpleString(anns)).append(", expected: ")
+ .append(toSimpleString(expectedAnns)).append("\n");
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ private static Annotation ann(Class<? extends Annotation> annotationType,
+ Object value) {
+ return AnnotationParser.annotationForMap(annotationType,
+ Collections.singletonMap("value", value));
+ }
+
+ private static String toSimpleString(List<Annotation> anns) {
+ return toSimpleString(anns.toArray(new Annotation[anns.size()]));
+ }
+
+ private static String toSimpleString(Annotation[] anns) {
+ StringJoiner joiner = new StringJoiner(", ");
+ for (Annotation ann : anns) {
+ joiner.add(toSimpleString(ann));
+ }
+ return joiner.toString();
+ }
+
+ private static String toSimpleString(Annotation ann) {
+ Class<? extends Annotation> annotationType = ann.annotationType();
+ Object value;
+ try {
+ value = annotationType.getDeclaredMethod("value").invoke(ann);
+ } catch (IllegalAccessException | InvocationTargetException
+ | NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ }
+ return "@" + annotationType.getSimpleName() + "(" + value + ")";
+ }
+
+ private static final Field classRedefinedCountField;
+
+ static {
+ try {
+ classRedefinedCountField = Class.class.getDeclaredField("classRedefinedCount");
+ classRedefinedCountField.setAccessible(true);
+ } catch (NoSuchFieldException e) {
+ throw new Error(e);
+ }
+ }
+
+ private static void incrementClassRedefinedCount(Class<?> clazz) {
+ try {
+ classRedefinedCountField.set(clazz,
+ ((Integer) classRedefinedCountField.get(clazz)) + 1);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}