Proxy.isProxyClass scalability

Peter Levart peter.levart at gmail.com
Fri Apr 19 14:36:23 UTC 2013


Hi Mandy,

On 04/19/2013 07:33 AM, Mandy Chung wrote:
>>
>> https://dl.dropboxusercontent.com/u/101777488/jdk8-tl/proxy-wc/webrev.02/index.html 
>>
>> What about package-private in java.lang.reflect? It makes Proxy 
>> itself much easier to read. When we decide which way to go, I can 
>> remove the interface and only leave a single package-private class...
>>
>
> thanks.  Moving it to a single package-private classin 
> java.lang.reflectand remove the interface sounds good.

Right.

>
> I have merged your patch with the recent TL repo and did some clean up 
> while reviewing it.  Some comments:
> 1. getProxyClass0 should validate the input interfaces and throw IAE 
> if any illegal argument (e.g. interfaces are not visible to the given 
> loader) before calling proxyClassCache.get(loader, interfaces). I 
> moved back the validation code from ProxyClassFactory.apply to 
> getProxyClass0.

Ops, you're right. There could be a request with interface(s) with same 
name(s) but loaded by different loader(s) and such code could return 
wrong pre-cached proxy class instead of throwing exception. 
Unfortunately, moving validation to slow-path was the cause of major 
performance and scalability improvement observed. With validation moved 
back to getProxyClass0 (in spite of using two-level WeakCache), we have 
the following performance (WeakCache#1):


Summary (4 Cores x 2 Threads i7 CPU):

Test                     Threads  ns/op Original WeakCache#1
=======================  =======  ============== ===========
Proxy_getProxyClass            1        2,403.27 2,174.51
                                4        3,039.01 2,555.00
                                8        5,193.58 4,273.42

Proxy_isProxyClassTrue         1           95.02 43.14
                                4        2,266.29 43.23
                                8        4,782.29 72.06

Proxy_isProxyClassFalse        1           95.02 1.36
                                4        2,186.59 1.36
                                8        4,891.15 2.72

Annotation_equals              1          240.10 195.68
                                4        1,864.06 201.41
                                8        8,639.20 337.46

It's a little better than original getProxyClass(), but not much. The 
isProxyClass() and consequently Annotation.equals() are far better 
though. But as you might have guessed, I kind of solved that too...

My first attempt was to optimize the interface validation. Only the 
"visibility" part is necessary to be performed on fast-path. Uniqueness 
and other things can be performed on slow-path. With split-validation 
and hacks like:

     private static final MethodHandle findLoadedClass0MH, 
findBootstrapClassMH;
     private static final ClassLoader dummyCL = new ClassLoader() {};

     static {
         try {
             Method method = 
ClassLoader.class.getDeclaredMethod("findLoadedClass0", String.class);
             method.setAccessible(true);
             findLoadedClass0MH = MethodHandles.lookup().unreflect(method);

             method = 
ClassLoader.class.getDeclaredMethod("findBootstrapClass", String.class);
             method.setAccessible(true);
             findBootstrapClassMH = 
MethodHandles.lookup().unreflect(method);
         } catch (NoSuchMethodException e) {
             throw (Error) new 
NoSuchMethodError(e.getMessage()).initCause(e);
         } catch (IllegalAccessException e) {
             throw (Error) new 
IllegalAccessError(e.getMessage()).initCause(e);
         }
     }

     static Class<?> findLoadedClass(ClassLoader loader, String name) {
         try {
             if (VM.isSystemDomainLoader(loader)) {
                 return (Class<?>) 
findBootstrapClassMH.invokeExact(dummyCL, name);
             } else {
                 return (Class<?>) 
findLoadedClass0MH.invokeExact(loader, name);
             }
         } catch (RuntimeException | Error e) {
             throw e;
         } catch (Throwable t) {
             throw new UndeclaredThrowableException(t);
         }
     }


... using findLoadedClass(loader, intf.getName()) and only doing 
Class.forName(intf.getName(), false, loader) if the former returned null 
... I managed to reclaim some performance (WeakCache#1+):


Test                     Threads  ns/op Original  WeakCache#1 WeakCache#1+
=======================  =======  ============== ===========  ============
Proxy_getProxyClass            1        2,403.27 2,174.51      1,589.36
                                4        3,039.01 2,555.00      1,929.11
                                8        5,193.58 4,273.42      3,409.77


...but that was still not very satisfactory.

I modified the KeyFactory to create keys that weakly-reference interface 
Class objects and implement hashCode/equals in terms of comparing those 
Class objects. With such keys, no false aliasing can occur and the whole 
validation can be pushed back to slow-path. I tried hard to create keys 
with as little space overhead as possible:

http://dl.dropboxusercontent.com/u/101777488/jdk8-tl/proxy-wc-wi/webrev.01/index.html

...but there can be no miracles. The good news is that the performance 
is back (WeakCache#2):


Summary (4 Cores x 2 Threads i7 CPU):

Test                     Threads  ns/op Original WeakCache#1  WeakCache#2
=======================  =======  ============== ===========  ===========
Proxy_getProxyClass            1        2,403.27 2,174.51       163.57
                                4        3,039.01 2,555.00       211.70
                                8        5,193.58 4,273.42       322.14

Proxy_isProxyClassTrue         1           95.02 43.14        41.23
                                4        2,266.29 43.23        42.20
                                8        4,782.29 72.06        72.21

Proxy_isProxyClassFalse        1           95.02 1.36         1.36
                                4        2,186.59 1.36         1.36
                                8        4,891.15 2.72         2.72

Annotation_equals              1          240.10 195.68       194.61
                                4        1,864.06 201.41       198.81
                                8        8,639.20 337.46       342.90


... and the most common usage (proxy class implementing exactly one 
interface) uses even less space than with interface-names-key - 16 bytes 
saved per proxy class vs. 8 bytes saved (32 bit addressing mode):

--------------------------------------
       Original j.l.r.Proxy
       1 interfaces / proxy class

class     proxy     size of   delta to
loaders   classes   caches    prev.ln.
--------  --------  --------  --------
        0         0       400       400
        1         1       768       368
        1         2       920       152
        1         3      1072       152
        1         4      1224       152
        1         5      1376       152
        1         6      1528       152
        1         7      1680       152
        1         8      1832       152
        2         9      2152       320
        2        10      2304       152
        2        11      2456       152
        2        12      2672       216
        2        13      2824       152
        2        14      2976       152
        2        15      3128       152
        2        16      3280       152

--------------------------------------
       Patched j.l.r.Proxy
       1 interfaces / proxy class

class     proxy     size of   delta to
loaders   classes   caches    prev.ln.
--------  --------  --------  --------
        0         0       240       240
        1         1       792       552
        1         2       928       136
        1         3      1064       136
        1         4      1200       136
        1         5      1336       136
        1         6      1472       136
        1         7      1608       136
        1         8      1744       136
        2         9      2088       344
        2        10      2224       136
        2        11      2360       136
        2        12      2560       200
        2        13      2696       136
        2        14      2832       136
        2        15      2968       136
        2        16      3104       136


Did you know, that Proxy.getProxyClass() can generate proxy classes 
implementing 0 interfaces? It can. There's exactly one such class 
generated per class loader:


--------------------------------------
       Original j.l.r.Proxy
       0 interfaces / proxy class

class     proxy     size of   delta to
loaders   classes   caches    prev.ln.
--------  --------  --------  --------
        0         0       400       400
        1         1       760       360
        2         2      1072       312

--------------------------------------
       Patched j.l.r.Proxy
       0 interfaces / proxy class

class     proxy     size of   delta to
loaders   classes   caches    prev.ln.
--------  --------  --------  --------
        0         0       240       240
        1         1       728       488
        2         2      1040       312


With 2 or more interfaces implemented by proxy class the space overhead 
increases faster than with original Proxy:


--------------------------------------
       Original j.l.r.Proxy
       2 interfaces / proxy class

class     proxy     size of   delta to
loaders   classes   caches    prev.ln.
--------  --------  --------  --------
        0         0       400       400
        1         1       768       368
        1         2       920       152
        1         3      1072       152
        1         4      1224       152
        1         5      1376       152
        1         6      1528       152
        1         7      1680       152
        1         8      1832       152
        2         9      2152       320
        2        10      2304       152
        2        11      2456       152
        2        12      2672       216
        2        13      2824       152
        2        14      2976       152
        2        15      3128       152
        2        16      3280       152

--------------------------------------
       Patched j.l.r.Proxy
       2 interfaces / proxy class

class     proxy     size of   delta to
loaders   classes   caches    prev.ln.
--------  --------  --------  --------
        0         0       240       240
        1         1       832       592
        1         2      1008       176
        1         3      1184       176
        1         4      1360       176
        1         5      1536       176
        1         6      1712       176
        1         7      1888       176
        1         8      2064       176
        2         9      2448       384
        2        10      2624       176
        2        11      2800       176
        2        12      3040       240
        2        13      3216       176
        2        14      3392       176
        2        15      3568       176
        2        16      3744       176

--------------------------------------
       Original j.l.r.Proxy
       3 interfaces / proxy class

class     proxy     size of   delta to
loaders   classes   caches    prev.ln.
--------  --------  --------  --------
        0         0       400       400
        1         1       776       376
        1         2       936       160
        1         3      1096       160
        1         4      1256       160
        1         5      1416       160
        1         6      1576       160
        1         7      1736       160
        1         8      1896       160
        2         9      2224       328
        2        10      2384       160
        2        11      2544       160
        2        12      2768       224
        2        13      2928       160
        2        14      3088       160
        2        15      3248       160
        2        16      3408       160

--------------------------------------
       Patched j.l.r.Proxy
       3 interfaces / proxy class

class     proxy     size of   delta to
loaders   classes   caches    prev.ln.
--------  --------  --------  --------
        0         0       240       240
        1         1       864       624
        1         2      1072       208
        1         3      1280       208
        1         4      1488       208
        1         5      1696       208
        1         6      1904       208
        1         7      2112       208
        1         8      2320       208
        2         9      2736       416
        2        10      2944       208
        2        11      3152       208
        2        12      3424       272
        2        13      3632       208
        2        14      3840       208
        2        15      4048       208
        2        16      4256       208

--------------------------------------
       Original j.l.r.Proxy
       4 interfaces / proxy class

class     proxy     size of   delta to
loaders   classes   caches    prev.ln.
--------  --------  --------  --------
        0         0       400       400
        1         1       776       376
        1         2       936       160
        1         3      1096       160
        1         4      1256       160
        1         5      1416       160
        1         6      1576       160
        1         7      1736       160
        1         8      1896       160
        2         9      2224       328
        2        10      2384       160
        2        11      2544       160
        2        12      2768       224
        2        13      2928       160
        2        14      3088       160
        2        15      3248       160
        2        16      3408       160

--------------------------------------
       Patched j.l.r.Proxy
       4 interfaces / proxy class

class     proxy     size of   delta to
loaders   classes   caches    prev.ln.
--------  --------  --------  --------
        0         0       240       240
        1         1       896       656
        1         2      1136       240
        1         3      1376       240
        1         4      1616       240
        1         5      1856       240
        1         6      2096       240
        1         7      2336       240
        1         8      2576       240
        2         9      3024       448
        2        10      3264       240
        2        11      3504       240
        2        12      3808       304
        2        13      4048       240
        2        14      4288       240
        2        15      4528       240
        2        16      4768       240


There's an increase of 8 bytes per proxy class key for each 2 interfaces 
added to proxy class in original Proxy code, but there's an increase of 
32 bytes per proxy class key for each single interface added in patched 
Proxy code.

I think the most common usage is to implement a single interface and 
there is 16 bytes gained for each such usage compared to original Proxy 
code.

> 2. I did some cleanup to restore some original comments to make the 
> diffs clearer where the change is.
> 3. I removed the newInstance method which is dead code after my 
> previous code.  Since we are in this code, I took the chance to clean 
> that up and also change a couple for-loop to use for-each.
>
> I have put the merged and slightly modified Proxy.java and webrev at:
> http://cr.openjdk.java.net/~mchung/jdk8/webrevs/7123493/webrev.00/
>
> We will use this bug for your contribution:
>    7123493 : (proxy) Proxy.getProxyClass doesn't scale under high load

I took j.l.r.Proxy file from your webrev and just changed the KeyFactory 
implementation. WeakCache is generic and can be used unchanged with 
either implementation of KeyFactory.

>
> For the weak cache class, since it's for proxy implementation use, I 
> suggest to take out the supportContainsValueOperation flagas it always 
> keeps the reverse map for isProxyClass lookup.
>
> You can simply call Objects.requireNonNull(param) instead of 
> requireNonNull(param, "param-name") since the proxy implementation 
> should have made sure the argument is non-null.
>
> Formatting nits:
>
>   68         Object cacheKey = CacheKey.valueOf(
>   69             key,
>   70             refQueue
>   71         );
>
> should be: all in one line or line break on a long-line.  Same for
> method signature.
>
>  237         void expungeFrom(
>  238             ConcurrentMap<?, ? extends ConcurrentMap<?, ?>> map,
>  239             ConcurrentMap<?, Boolean> reverseMap
>  240         );
>
> should be:
>
> void expungeFrom(ConcurrentMap<?, ? extends ConcurrentMap<?, ?>> map,
>                  ConcurrentMap<?, Boolean> reverseMap);
>
> so that it'll be more consistent with the existing code.  I'll do a
> detailed review on the weak cache class as you will finalize the code
> per the decision to go with the two-level weak cache.

I hope I have addressed all that in above webrev.

Regards, Peter

>
> Thanks again for the good work.
>
> Mandy
> [1] http://openjdk.java.net/jeps/161 




More information about the core-libs-dev mailing list