Proxy.isProxyClass scalability

Peter Levart peter.levart at
Wed Apr 10 12:35:24 UTC 2013

Hi Alan,

I have prepared new webrev of the patch rebased to current tip of 
jdk8/tl repo:

I noticed there were quite a few changes to the j.l.r.Proxy in the 
meanwhile regarding new security checks. But @CallerSensitive changes 
seem not to have been pushed yet.

Anyway, I have also simplified the caching logic a bit so that it's now 
easier to reason about it's correctness. I have re-run the performance 
benchmarks that are still very favourable:

Proxy.getProxyClass(): is almost 15x faster single-threaded with same or 
even slightly better scalability
Proxy.isProxyClass() == true: is about 9x faster for single-threaded 
execution and much more scalable
Proxy.isProxyClass() == false: is about 1600x faster single-threaded and 
infinitely scalable
Annotation.equals(): is 1.6x faster single-threaded and much more scalable

I also devised an alternative caching mechanism with scalability in mind 
which uses WeakReferences for keys (for example ClassLoader) and values 
(for example Class) that could be used in this situation in case adding 
a field to ClassLoader is not an option:

Regards, Peter

On 01/28/2013 05:13 PM, Peter Levart wrote:
> Hi Alan,
> I prepared the variant with lazy initialization of ConcurrentHashMaps 
> per ClassLoader and performance measurements show no differences. So 
> here's this variant:
> *
> I also checked the ClassLoader layout and as it happens the additional 
> pointer slot increases the ClassLoader object size by 8 bytes in both 
> addressing modes: 32bit and 64bit. But that's a small overhead 
> compared to for example the deep-size of AppClassLoader at the 
> beginning of the main method: 14648 bytes (32bit) / 22432 bytes (64bit).
> Regards, Peter
> On 01/28/2013 01:57 PM, Peter Levart wrote:
>> On 01/28/2013 12:49 PM, Alan Bateman wrote:
>>> On 25/01/2013 17:55, Peter Levart wrote:
>>>> :
>>>> The solution is actually very simple. I just want to validate my 
>>>> reasoning before jumping to implement it:
>>>> - for solving scalability of getProxyClass cache, a field with a 
>>>> reference to ConcurrentHashMap<List<String>, Class<? extends 
>>>> Proxy>> is added to j.l.ClassLoader
>>>> - for solving scalability of isProxyClass, a field with a reference 
>>>> to ConcurrentHashMap<Class<? extends Proxy>, Boolean> is added to 
>>>> j.l.ClassLoader
>>> I haven't had time to look very closely as your more recent changes 
>>> (you are clearly doing very good work here). The only thing I wonder 
>>> if whether it would be possible to avoid adding to ClassLoader. I 
>>> can't say what percentage of frameworks and applications use proxies 
>>> but it would be nice if the impact on applications that don't use 
>>> proxies is zero.
>> Hi Alan,
>> Hm, well. Any application that uses run-time annotations, is 
>> implicitly using Proxies. But I agree that there are applications 
>> that don't use either. Such applications usually don't use many 
>> ClassLoaders either. Applications that use many ClassLoaders are 
>> typically application servers or applications written for modular 
>> systems (such as OSGI or NetBeans) and all those applications are 
>> also full of runtime annotations nowadays. So a typical application 
>> that does not use neither Proxies nor runtime annotations is composed 
>> of bootstrap classloader, AppClassLoader and ExtClassLoader. The 
>> ConcurrentHashMap for the bootstrap classloader is hosted by 
>> j.l.r.Proxy class and is only initialized when the j.l.r.Proxy class 
>> is initialized - so in this case never. The overhead for such 
>> applications is therefore an empty ConcurrentHashMap instance plus 
>> the overhead for a pointer slot in the ClassLoader object multiplied 
>> by the number of ClassLoaders (typically 2). An empty 
>> ConcurrentHashMap in JDK8 is only pre-allocating a single internal 
>> Segment:
>> java.util.concurrent.ConcurrentHashMap at 642b6fc7(48 bytes) {
>>   keySet: null
>>   values: null
>>   hashSeed: int
>>   segmentMask: int
>>   segmentShift: int
>>   segments: 
>> java.util.concurrent.ConcurrentHashMap$Segment[16]@8e1dfb1(80 bytes) {
>> java.util.concurrent.ConcurrentHashMap$Segment at 2524e205(40 bytes) {
>>       sync: 
>> java.util.concurrent.locks.ReentrantLock$NonfairSync at 17feafba(32 
>> bytes) {
>>         exclusiveOwnerThread: null
>>         head: null
>>         tail: null
>>         state: int
>>       }->(32 deep bytes)
>>       table: 
>> java.util.concurrent.ConcurrentHashMap$HashEntry[2]@1c3aacb4(24 bytes) {
>>         null
>>         null
>>       }->(24 deep bytes)
>>       count: int
>>       modCount: int
>>       threshold: int
>>       loadFactor: float
>>     }->(96 deep bytes)
>>     null
>>     null
>>     null
>>     null
>>     null
>>     null
>>     null
>>     null
>>     null
>>     null
>>     null
>>     null
>>     null
>>     null
>>     null
>>   }->(176 deep bytes)
>>   keySet: null
>>   entrySet: null
>>   values: null
>> }->(224 deep bytes)
>> ...therefore the overhead is approx. 224+4 = 228 bytes (on 32 bit 
>> pointer environments) per ClassLoader. In typical application (with 2 
>> ClassLoader objects) this amounts to approx. 456 bytes.
>> Is 456 bytes overhead too much?
>> If it is, I could do lazy initialization of per-classloader CHM 
>> instances, but then the fast-path would incur a little additional 
>> penalty (not to be taken seriously though).
>> Regards, Peter
>> P.S. I was inspecting the ClassValue internal implementation. This is 
>> a very nice piece of Java code. Without using any Unsafe magic, it 
>> provides a perfect performant an scalable map of lazily initialized 
>> independent data structures associated with Class instances. There's 
>> nothing special about ClassValue/ClassValueMap that would tie it to 
>> Class instances. In fact I think the ClassValueMap could be made 
>> generic so it could be reused for implementing a ClasLoaderValue, for 
>> example. This would provide a more performant and scalable 
>> alternative to using WeakHashMap<ClassLoader, ...> wrapped by 
>> synchronized wrappers for other uses.
>> If anything like that happens in the future, the proposed patch can 
>> be quickly adapted to using that infrastructure instead of a direct 
>> reference in the ClassLoader.
>>> -Alan

More information about the core-libs-dev mailing list