8011940: java.lang.Class.getAnnotations() always enters synchronized method
Reviewed-by: jfranck, chegar, psandoz, shade
This commit is contained in:
parent
100b98aafa
commit
c7759dc643
@ -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<T> 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<T> implements java.io.Serializable,
|
||||
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 @@ public final class Class<T> implements java.io.Serializable,
|
||||
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 final class Class<T> implements java.io.Serializable,
|
||||
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 final class Class<T> implements java.io.Serializable,
|
||||
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 final class Class<T> implements java.io.Serializable,
|
||||
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 final class Class<T> implements java.io.Serializable,
|
||||
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);
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
private volatile transient AnnotationData annotationData;
|
||||
|
||||
// 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 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 synchronized void initAnnotationsIfNecessary() {
|
||||
clearAnnotationCachesOnClassRedefinition();
|
||||
if (annotations != null)
|
||||
return;
|
||||
declaredAnnotations = AnnotationParser.parseAnnotations(
|
||||
getRawAnnotations(), getConstantPool(), this);
|
||||
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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user