Proxy.isProxyClass scalability

Mandy Chung mandy.chung at oracle.com
Sun Apr 21 02:52:58 UTC 2013


Hi Peter,

I want to give it some more thought and discuss with you next week. As 
for the zero number of interface, I think it's a bug and it should throw 
IAE if the given interfaces array is empty.   Thanks for finding it and 
I'll file a separate bug for that since it requires spec 
update/clarification.

Mandy

On 4/20/2013 12:31 AM, Peter Levart wrote:
> Hi Mandy,
>
> I have another idea. Before jumping to implement it, I will first ask 
> what do you think of it. For example:
>
> - have an optimal interface names key calculated from interfaces.
> - visibility of interfaces and other validations are pushed to 
> slow-path (inside ProxyClassFactory.apply)
> - the proxy Class object returned from WeakCache.get() is 
> post-validated with a check like:
>
> for (Class<?> intf : interfaces) {
>     if (!intf.isAssignableFrom(proxyClass)) {
>         throw new IllegalArgumentException();
>     }
> }
> // return post-validated proxyClass from getProxyClass0()...
>
> I feel that Class.isAssignableFrom(Class) check could be much faster 
> and that the only reason the check can fail is if some interface is 
> not visible from the class loader.
>
> Am I correct?
>
> Regards, Peter
>
>
>
> On 04/19/2013 04:36 PM, Peter Levart wrote:
>> 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