From ef18263234ba18591d535ebc2b8b0538da8df22b Mon Sep 17 00:00:00 2001 From: plevart Date: Thu, 19 Sep 2013 16:14:13 +0200 Subject: [PATCH] 8011940: java.lang.Class.getAnnotations() always enters synchronized method Reviewed-by: jfranck, chegar, psandoz, shade --- src/share/classes/java/lang/Class.java | 119 ++++++---- ...tionsInheritanceOrderRedefinitionTest.java | 210 ++++++++++++++++++ 2 files changed, 289 insertions(+), 40 deletions(-) create mode 100644 test/java/lang/annotation/AnnotationsInheritanceOrderRedefinitionTest.java diff --git a/src/share/classes/java/lang/Class.java b/src/share/classes/java/lang/Class.java index f884f2efe..a09aa2c86 100644 --- a/src/share/classes/java/lang/Class.java +++ b/src/share/classes/java/lang/Class.java @@ -48,6 +48,7 @@ import java.util.ArrayList; 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 @@ public final class Class implements java.io.Serializable, 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 @@ public final class Class implements java.io.Serializable, AnnotationType newType) { return unsafe.compareAndSwapObject(clazz, annotationTypeOffset, oldType, newType); } + + static boolean casAnnotationData(Class clazz, + AnnotationData oldData, + AnnotationData newData) { + return unsafe.compareAndSwapObject(clazz, annotationDataOffset, oldData, newData); + } } /** @@ -2406,7 +2416,7 @@ public final class Class implements java.io.Serializable, private static boolean useCaches = true; // reflection data that might get invalidated when JVM TI RedefineClasses() is called - static class ReflectionData { + private static class ReflectionData { volatile Field[] declaredFields; volatile Field[] publicFields; volatile Method[] declaredMethods; @@ -3253,8 +3263,7 @@ public final class Class implements java.io.Serializable, public A getAnnotation(Class annotationClass) { Objects.requireNonNull(annotationClass); - initAnnotationsIfNecessary(); - return (A) annotations.get(annotationClass); + return (A) annotationData().annotations.get(annotationClass); } /** @@ -3275,16 +3284,14 @@ public final class Class implements java.io.Serializable, public A[] getAnnotationsByType(Class 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 final class Class implements java.io.Serializable, public A getDeclaredAnnotation(Class annotationClass) { Objects.requireNonNull(annotationClass); - initAnnotationsIfNecessary(); - return (A) declaredAnnotations.get(annotationClass); + return (A) annotationData().declaredAnnotations.get(annotationClass); } /** @@ -3308,52 +3314,85 @@ public final class Class implements java.io.Serializable, public A[] getDeclaredAnnotationsByType(Class 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); + } + + // annotation data that might get invalidated when JVM TI RedefineClasses() is called + private static class AnnotationData { + final Map, Annotation> annotations; + final Map, Annotation> declaredAnnotations; + + // Value of classRedefinedCount when we created this AnnotationData instance + final int redefinedCount; + + AnnotationData(Map, Annotation> annotations, + Map, Annotation> declaredAnnotations, + int redefinedCount) { + this.annotations = annotations; + this.declaredAnnotations = declaredAnnotations; + this.redefinedCount = redefinedCount; + } } // Annotations cache - private transient Map, Annotation> annotations; - private transient Map, Annotation> declaredAnnotations; - // Value of classRedefinedCount when we last cleared the cached annotations and declaredAnnotations fields - private transient int lastAnnotationsRedefinedCount = 0; - - // Clears cached values that might possibly have been obsoleted by - // a class redefinition. - private void clearAnnotationCachesOnClassRedefinition() { - if (lastAnnotationsRedefinedCount != classRedefinedCount) { - annotations = declaredAnnotations = null; - lastAnnotationsRedefinedCount = classRedefinedCount; - } - } - - private synchronized void initAnnotationsIfNecessary() { - clearAnnotationCachesOnClassRedefinition(); - if (annotations != null) - return; - declaredAnnotations = AnnotationParser.parseAnnotations( - getRawAnnotations(), getConstantPool(), this); + @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, Annotation> declaredAnnotations = + AnnotationParser.parseAnnotations(getRawAnnotations(), getConstantPool(), this); Class superClass = getSuperclass(); - if (superClass == null) { - annotations = declaredAnnotations; - } else { - annotations = new HashMap<>(); - superClass.initAnnotationsIfNecessary(); - for (Map.Entry, Annotation> e : superClass.annotations.entrySet()) { + Map, Annotation> annotations = null; + if (superClass != null) { + Map, Annotation> superAnnotations = + superClass.annotationData().annotations; + for (Map.Entry, Annotation> e : superAnnotations.entrySet()) { Class annotationClass = e.getKey(); - if (AnnotationType.getInstance(annotationClass).isInherited()) + 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 { + // 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 diff --git a/test/java/lang/annotation/AnnotationsInheritanceOrderRedefinitionTest.java b/test/java/lang/annotation/AnnotationsInheritanceOrderRedefinitionTest.java new file mode 100644 index 000000000..909154a7c --- /dev/null +++ b/test/java/lang/annotation/AnnotationsInheritanceOrderRedefinitionTest.java @@ -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 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 annotationType, + Object value) { + return AnnotationParser.annotationForMap(annotationType, + Collections.singletonMap("value", value)); + } + + private static String toSimpleString(List 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 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); + } + } +} -- GitLab