AnnotationType & AnnotationSupport + repeating annotations

Peter Levart peter.levart at gmail.com
Tue Dec 4 09:23:10 UTC 2012


Hi again,

Zhong Yu suggested to use plain TheadLocal<Map<Class, ...>> instead of 
ClassValue<ThreadLocal> in the proposed patch. Here's the updated webrev 
that contains this change:

http://dl.dropbox.com/u/101777488/jdk8-tl/AnnotationTypeSupport/webrev.02/index.html

The improvement is twofold:
- There is no ClassValue.ClassValueMap installed on the annotation Class 
instances
- The mechanism for exposing an instance of AnnotationType for recursive 
calls in the same thread only uses the ThreadLocal entry for the 
in-flight recursive call and cleans-up and removes the entry when the 
call stack unwinds, so no unused retained objects are left behind.

The results of the benchmarks are not affected.

Regards, Peter

On 12/03/2012 10:52 PM, Peter Levart wrote:
> Hi David and Alan,
>
> The following is in part connected with the new code for repeating 
> annotations, and in part with the proposed patch for resolving 
> synchronization issues with annotations as well as improvements 
> regarding space and multi-threaded contention.
>
> Although connected, the patch proposed here can be taken entirely by 
> itself. Let me try to explain what this patch does. This patch removes 
> the "synchronized" keyword from the static method 
> AnnotationType.getInstance(Class). By itself this synchronization does 
> not present any big performance issue since the method is always 
> called on slow-path (when parsing the annotations, when lazily 
> constructing a Map of inherited annotations, when de-serializing 
> annotations). The reason this method is synchronized on a single 
> monitor is because it:
> - lazily constructs an instance of AnnotationType and exposes it to 
> other threads via a Class.annotationType field
> - in the middle of the AnnotationType constructor it exposes a 
> half-constructed instance via a Class.annotationType field to current 
> thread so that recursive calls don't result in infinite recursion.
>
> As current parsing/resolving logic is coded, other threads must not 
> see the half-constructed AnnotationType instance and current thread 
> must see it. This is achieved by single re-entrant lock because only 
> single lock guarantees the absence of dead-locks (as can be seen from 
> bugs this lock in combination with initAnnotationsIfNecessary() is a 
> cause for dead-locks, but that's another story).
>
> Now because there is a single lock, the method must not be called on 
> heavily executed code paths or this will present a synchronization 
> bottleneck. One such heavily executed code path is in the new 
> sun.reflect.annotation.AnnotationSupport class that contains the logic 
> for repeating annotations. In this class the AnnotationType for a 
> particular annotation class is not obtained via this synchronized 
> method, but incorrectly via direct unsynchronized read of 
> Class.annotationType field. The code in AnnotationSupport can 
> therefore dereference a half-constructed AnnotationType instance 
> before it's constructor, executing in another thread, is finished and 
> before final fields in object are frozen.
>
> Class.[get,set]AnnotationType should only be called from within the 
> synchronized AnnotationType.getInstance method, but that apparently is 
> to contended to be practical.
>
> I solved the problem by:
> - making AnnotationType an immutable object (all fields final)
> - exposing the instance to other threads via an unsynchronized write 
> to Class.annotationType field only after constructor is finished and 
> final fields are frozen
> - exposing the instance to current thread for recursive calls in the 
> middle of the constructor via a special:
>
> private static final ClassValue<ThreadLocal<AnnotationType>> 
> IN_CONSTRUCTION = ...
>
> field.
>
> It's true, this does present an overhead in storage, since every Class 
> instance for annotation type will have a ClassValue.ClassValueMap 
> installed, but it is hoped that the number of different annotation 
> classes is not big compared to the number of all classes. A 
> ThreadLocal referenced by ClassValue is only set for the in-flight 
> recursive call and cleared afterwards.
>
> Because with such non-blocking access to AnnotationType, 
> AnnotationType.getInstance() can be used in AnnotationSupport properly 
> to quickly access the AnnotationType instances. The access to 
> AnnotationType with this patch is so quick that I added 2 fields to it 
> (container, containee) where the container and/or containee for a 
> particular annotation type are cached and used in AnnotationSupport to 
> resolve repeating annotations. This is much faster than invoking 
> Class.getDirectDeclaredAnnotation() which is a HashMap.get, followed 
> by reflective invocation of the "value" method on that annotation.
>
> The patch is here:
>
> http://dl.dropbox.com/u/101777488/jdk8-tl/AnnotationTypeSupport/webrev.01/index.html 
>
>
> The benchmarks that show improvement are the same benchmarks used in 
> my related proposed patch (Class.getAnnotations() bottleneck):
>
> https://raw.github.com/plevart/jdk8-tl/JEP-149/test/src/test/ReflectionTest.java 
>
>
> The results are combined results using plain JDK8 code with repeating 
> annotation and then one patch applied at a time and finally both 
> patches combined:
>
> https://raw.github.com/plevart/jdk8-tl/JEP-149/test/benchmark_results_i7-2600K.txt 
>
>
>
> Regards, Peter
>
> P.S.
>
> Maybe this is not the best approach. Another approach would be to 
> construct a special non-recursive variant of annotation parsing logic 
> that would be used only for select meta-annotations - just enough to 
> construct an AnnotationType with all it's data.
>
> The proposed patch is trying to keep the semantics of original logic, 
> which is not entirely correct. The patch is not trying to solve the 
> logic in-correctness. Here's a contrived example that exposes the 
> in-correctness:
>
> Let's have the following two annotations:
>
> @Retention(RetentionPolicy.RUNTIME)
> @RuntimeAnnotationB
> public @interface RuntimeAnnotationA {}
>
> @Retention(RetentionPolicy.RUNTIME)
> @RuntimeAnnotationA
> public @interface RuntimeAnnotationB {}
>
> and the following test:
>
> @RuntimeAnnotationA
> public class AnnotationRetentionTest {
>     public static void main(String[] args) {
>         RuntimeAnnotationA ann1 = 
> AnnotationRetentionTest.class.getDeclaredAnnotation(RuntimeAnnotationA.class);
>         System.out.println(ann1 != null);
>         RuntimeAnnotationA ann2 = 
> RuntimeAnnotationB.class.getDeclaredAnnotation(RuntimeAnnotationA.class);
>         System.out.println(ann2 != null);
>     }
> }
>
>
> The test, when run, prints:
>
> true
> true
>
> Now change the RuntimeAnnotationA's retention policy to:
>
> @Retention(RetentionPolicy.CLASS)
> @RuntimeAnnotationB
> public @interface RuntimeAnnotationA {}
>
> ...and only recompile the RuntimeAnnotationA.
> When the test is run again with the recompiled RuntimeAnnotationA it 
> prints:
>
> false
> true
>
>
> Although the annotation data for RuntimeAnnotationA is present in both 
> AnnotationRetentionTest and RuntimeAnnotationB class files, the order 
> of initialization and recursion causes the RuntimeAnnotationA to be 
> loaded as RUNTIME annotation into the RuntimeAnnotationB.class (WRONG) 
> but not into the AnnotationRetentionTest.class (CORRECT).
>




More information about the core-libs-dev mailing list