Proxy.isProxyClass scalability (original) (raw)
Peter Levart peter.levart at gmail.com
Wed Apr 24 06:58:48 UTC 2013
- Previous message: Proxy.isProxyClass scalability
- Next message: Proxy.isProxyClass scalability
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
Hi Mandy,
On 04/24/2013 01:43 AM, Mandy Chung wrote:
Hi Peter,
Sorry for the delay. 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? The isAssignableFrom check should be correct for well-behaved class loaders [1]. However, for non well-behaved class loaders, I'm not absolutely confident that this is right. The case that I was concerned is when intf.isAssignableFrom(proxyClass) returns true but the proxy class doesn't implement the runtime types (i.e. given interfaces). Precise check should be to validate if the given interfaces == the proxy interfaces implemented by the cached proxy class (i.e. proxyClass.getInterfaces()).
Really? This can happen? Could you describe a situation when?
Class.isAssignableFrom method checks if the proxy class is a subtype of the interface and no class loading/resolution involved. It's definitely much cheaper than Class.forName which involves checking if a class is loaded (class loader delegation and class file search if not loaded - which is not the case you measure). The existing implementation uses the interface names as the key to the per-loader proxy class cache. I think we should use the Class<?>[] as the key (your webrev.02). I have quickly modified the version I sent you (simply change the Key class to use Class<?>[] rather than String[] but didn't change the concurrent map cache to keep weak references) and the benchmark shows comparable speedup.
I just noticed the following (have been thinking of it already, but then forgot) in original code:
/* 512 * Note that we need not worry about reaping the cache for 513 * entries with cleared weak references because if a proxy class 514 * has been garbage collected, its class loader will have been 515 * garbage collected as well, so the entire cache will be reaped 516 * from the loaderToCache map. 517 */
Hmm... I think the original code has the class loader leak. The WeakHashMap<ClassLoader, HashMap<List, Class>> keeps a weak reference to the ClassLoader but a strong reference to the cache of the proxy classes that are defined by that class loader. The proxy classes are not GC'ed and thus the class loader is still kept alive.
Original code is actualy using the following structure: WeakHashMap<ClassLoader, HashMap<List, WeakReference>> ... so no ClassLoader leak here...
The TwoLevelWeakCache is an analogous structure: ConcurrentHashMap<WeakReference, ConcurrentHashMap<SubKey, WeakReference>>
The point I was trying to make is that it is not needed to register a ReferenceQueue with WeakReference values and remove entries as they are cleared and enqueued, because they will not be cleared until the ClassLoader that defines the Class-es is GC-ed and the expunging logic can work solely on the grounds of cleared and enqueued WeakReference keys, which is what my latest webrev using String-based sub-keys does (I have to update the other too).
Each ClassLoader maintains explicit hard-references to all Class objects for classes defined by the loader. So proxy Class object can not be GC-ed until the ClassLoader is GC-ed. So we need not register the CacheValue objects in WeakCache with a refQueue. The expunging of reverseMap entries is already performed with CacheKey when it is cleared and equeued. There's no harm as it is, since the clean-up is performed with all the checks and is idempotent, but it need not be done for ClassValue objects holding weak references to proxy Class objects. As explained above, for the per-loader proxy class cache, both the key (interfaces) and the proxy class should be wrapped with weak reference.
Not the key (interfaces array) but the individual interface Class object
- each has to be separately wrapped with a WeakReference. If you just do the WeakReference<Class[]> it will quickly be cleared since no-one is holding a strong reference to the array object.
The following webrev was an attempt in that direction (notice different name in URL: proxy-wc-wi - WeakCache-WeakInterfaces - I maintain separate numbering for webrevs in this branch):
http://dl.dropboxusercontent.com/u/101777488/jdk8-tl/proxy-wc-wi/webrev.02/index.html
In your revisions, you optimize for 0-interface and 1-interface proxy class. What I hacked up earlier was just to use Class<?>[] as the key (need to make a copy of the array to prevent that being mutated during runtime) that is a simpler and straightforward implementation. I didn't measure the footprint and compare the performance of your versions. Have you seen any performance difference which led you to make the recent changes?
I developed two different approaches:
- Key made of WeakReference-s to interface Class objects. Strong points:
- no key aliasing, validation can be pushed entirely to slow-path
- quick and scalable
- less memory usage than original code for 0 and 1-interface proxies Weak points:
- more memory usage for 2+ -interface proxies Latest webrev: http://dl.dropboxusercontent.com/u/101777488/jdk8-tl/proxy-wc-wi/webrev.02/index.html Comments: I like this one. If 2+ -interface proxies are relatively rare and you don't mind extra space consumption for them, I would go with this approach.
- Key made of interned Strings (names of interfaces) Strong points:
- quick and scalable
- much less memory usage than original code for all variations of interface counts and in particular for 0, 1 and 2-interface proxies Weak points:
- key is aliased, so the validation of interfaces has to be done - I tried to do it post-festum with intf.isAssignableFrom(proxyClass) but you say that is not reliable. If the validation is performed on fast-path before proxy class is obtained, using Class.forName, the slow-down is huge. Latest webrev: http://dl.dropboxusercontent.com/u/101777488/jdk8-tl/proxy-wc/webrev.03/index.html Comments: This is the best space-saving approach. With tricks for single and two-interface keys, the savings are noticable.
So, I would really like to understand the situation where intf.isAssignableFrom(proxyClass) returns true, but proxyClass does not implement the interface. Maybe we could work-it out somehow.
Are you thinking of a situation like:
- intf1: pkg.Interface, loaded by classloader1
- intf2: pkg.SubInterface extends pkg.Interface, loaded by classloader1
- intf3: pkg.Interface extends pkg.SubInterface, loaded by classloader2 which is child of classloader1
Now you call:
proxy3 = Proxy.getProxyClass(classloader2, intf3);
followed by:
proxy1 = Proxy.getProxyClass(classloader2, intf1);
Is it possible that the second call succeeds and returns proxy1 == proxy3 ?
Regards, Peter
Mandy [1] http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html#jvms-5.3
- Previous message: Proxy.isProxyClass scalability
- Next message: Proxy.isProxyClass scalability
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]