Hi, There are several places in our codebase were we need/want to cache stuff that is associated with a class or class loader. Currently these places retain a strong reference to the class loader, which is not correct, because the class loader should be garbage collectable.
I cooked up proposal for an API to use for this and implemented it in the three places I could think of off the top my head (serialization, proxy and resource bundle). This is just a first stab, so no documentation and no change log entry yet. Please comment on the idea and/or the API. Regards, Jeroen
? gnu/classpath/ClassLoaderCache.java ? vm/reference/gnu/classpath/VMClassLoaderCache.java Index: java/io/ObjectStreamClass.java =================================================================== RCS file: /cvsroot/classpath/classpath/java/io/ObjectStreamClass.java,v retrieving revision 1.45 diff -u -r1.45 ObjectStreamClass.java --- java/io/ObjectStreamClass.java 21 May 2006 20:53:14 -0000 1.45 +++ java/io/ObjectStreamClass.java 21 Aug 2006 09:20:19 -0000 @@ -39,6 +39,7 @@ package java.io; +import gnu.classpath.ClassLoaderCache; import gnu.java.io.NullOutputStream; import gnu.java.lang.reflect.TypeSignature; import gnu.java.security.action.SetAccessibleAction; @@ -100,14 +101,15 @@ if (cl == null) return null; - ObjectStreamClass osc = (ObjectStreamClass) classLookupTable.get(cl); + ObjectStreamClass osc = + (ObjectStreamClass) classLookupTable.get(cl.getClassLoader(), cl); if (osc != null) return osc; else { osc = new ObjectStreamClass(cl); - classLookupTable.put(cl, osc); + classLookupTable.put(cl.getClassLoader(), cl, osc); return osc; } } @@ -948,7 +950,7 @@ public static final ObjectStreamField[] NO_FIELDS = {}; - private static Hashtable classLookupTable = new Hashtable(); + private static ClassLoaderCache classLookupTable = new ClassLoaderCache(); private static final NullOutputStream nullOutputStream = new NullOutputStream(); private static final Comparator interfaceComparator = new InterfaceComparator(); private static final Comparator memberComparator = new MemberComparator(); Index: java/lang/ClassLoader.java =================================================================== RCS file: /cvsroot/classpath/classpath/java/lang/ClassLoader.java,v retrieving revision 1.61 diff -u -r1.61 ClassLoader.java --- java/lang/ClassLoader.java 23 Apr 2006 09:52:37 -0000 1.61 +++ java/lang/ClassLoader.java 21 Aug 2006 09:19:13 -0000 @@ -57,6 +57,7 @@ import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; +import java.util.IdentityHashMap; import java.util.Map; import java.util.StringTokenizer; @@ -145,6 +146,11 @@ */ private final boolean initialized; + /** + * Cache field for use by gnu.classpath.ClassLoaderCache. + */ + IdentityHashMap cache; + static class StaticData { /** Index: java/lang/reflect/Proxy.java =================================================================== RCS file: /cvsroot/classpath/classpath/java/lang/reflect/Proxy.java,v retrieving revision 1.26 diff -u -r1.26 Proxy.java --- java/lang/reflect/Proxy.java 20 Feb 2006 20:21:26 -0000 1.26 +++ java/lang/reflect/Proxy.java 21 Aug 2006 09:21:40 -0000 @@ -38,6 +38,7 @@ package java.lang.reflect; +import gnu.classpath.ClassLoaderCache; import gnu.java.lang.reflect.TypeSignature; import java.io.Serializable; @@ -167,12 +168,8 @@ /** * Map of ProxyType to proxy class. - * - * @XXX This prevents proxy classes from being garbage collected. - * java.util.WeakHashSet is not appropriate, because that collects the - * keys, but we are interested in collecting the elements. */ - private static final Map proxyClasses = new HashMap(); + private static final ClassLoaderCache proxyClasses = new ClassLoaderCache(); /** * The invocation handler for this proxy instance. For Proxy, this @@ -260,7 +257,7 @@ { interfaces = (Class[]) interfaces.clone(); ProxyType pt = new ProxyType(loader, interfaces); - Class clazz = (Class) proxyClasses.get(pt); + Class clazz = (Class) proxyClasses.get(loader, pt); if (clazz == null) { if (VMProxy.HAVE_NATIVE_GET_PROXY_CLASS) @@ -276,7 +273,7 @@ : new ClassFactory(data).generate(loader)); } - Object check = proxyClasses.put(pt, clazz); + Object check = proxyClasses.put(loader, pt, clazz); // assert check == null && clazz != null; if (check != null || clazz == null) throw new InternalError(/*"Fatal flaw in getProxyClass"*/); @@ -364,7 +361,7 @@ return false; // This is a linear search, even though we could do an O(1) search // using new ProxyType(clazz.getClassLoader(), clazz.getInterfaces()). - return proxyClasses.containsValue(clazz); + return proxyClasses.containsValue(clazz.getClassLoader(), clazz); } /** Index: java/util/ResourceBundle.java =================================================================== RCS file: /cvsroot/classpath/classpath/java/util/ResourceBundle.java,v retrieving revision 1.38 diff -u -r1.38 ResourceBundle.java --- java/util/ResourceBundle.java 13 Aug 2006 01:06:19 -0000 1.38 +++ java/util/ResourceBundle.java 21 Aug 2006 09:23:23 -0000 @@ -39,6 +39,7 @@ package java.util; +import gnu.classpath.ClassLoaderCache; import gnu.classpath.VMStackWalker; import java.io.IOException; @@ -121,11 +122,13 @@ * * @see BundleKey */ - private static Map bundleCache = new LinkedHashMap(CACHE_SIZE + 1, 0.75F, true) - { - public boolean removeEldestEntry(Map.Entry entry) - { - return size() > CACHE_SIZE; + private static ClassLoaderCache bundleCache = new ClassLoaderCache() { + protected Map newMap() { + return new LinkedHashMap(CACHE_SIZE + 1, 0.75F, true) { + public boolean removeEldestEntry(Map.Entry entry) { + return size() > CACHE_SIZE; + } + }; } }; @@ -385,7 +388,7 @@ Locale defaultLocale = Locale.getDefault(); // This will throw NullPointerException if any arguments are null. lookupKey.set(defaultLocale, baseName, locale, classLoader); - Object obj = bundleCache.get(lookupKey); + Object obj = bundleCache.get(classLoader, lookupKey); if (obj instanceof ResourceBundle) return (ResourceBundle) obj; @@ -406,14 +409,14 @@ if (bundle == null) { // Cache the fact that this lookup has previously failed. - bundleCache.put(key, nullEntry); + bundleCache.put(classLoader, key, nullEntry); throw new MissingResourceException("Bundle " + baseName + " not found for locale " + locale + " by classloader " + classLoader, baseName, ""); } // Cache the result and return it. - bundleCache.put(key, bundle); + bundleCache.put(classLoader, key, bundle); return bundle; }
ClassLoaderCache.java
Description: ClassLoaderCache.java
VMClassLoaderCache.java
Description: VMClassLoaderCache.java