Hi,

I've filed a bug for this,

http://bugs.sun.com/view_bug.do?bug_id=8004919

should hopefully be visible shortly.

I won't get around to fixing this just yet, but thanks for both the 
investigation and the suggested fix!

cheers
/Joel

On Dec 4, 2012, at 10:23 AM, Peter Levart <peter.lev...@gmail.com> wrote:

> 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).
>> 
> 

Reply via email to