From: aclement Date: Mon, 21 Aug 2006 15:23:58 +0000 (+0000) Subject: some updates to ClassLoaderRepository - tested by RontimeWeaving X-Git-Tag: BEFORE_133532~92 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=0185a0214f790c6611b48b986e01ef97a399b6ae;p=aspectj.git some updates to ClassLoaderRepository - tested by RontimeWeaving --- diff --git a/bcel-builder/src/org/aspectj/apache/bcel/util/ClassLoaderRepository.java b/bcel-builder/src/org/aspectj/apache/bcel/util/ClassLoaderRepository.java index 6c30dade8..2f7325186 100644 --- a/bcel-builder/src/org/aspectj/apache/bcel/util/ClassLoaderRepository.java +++ b/bcel-builder/src/org/aspectj/apache/bcel/util/ClassLoaderRepository.java @@ -57,11 +57,15 @@ package org.aspectj.apache.bcel.util; import java.io.IOException; import java.io.InputStream; import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; import java.lang.ref.SoftReference; import java.net.URL; import java.net.URLClassLoader; +import java.util.AbstractMap; +import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Set; import java.util.WeakHashMap; import org.aspectj.apache.bcel.classfile.ClassParser; @@ -76,32 +80,35 @@ import org.aspectj.apache.bcel.classfile.JavaClass; * * @see org.aspectj.apache.bcel.Repository * - * @version $Id: ClassLoaderRepository.java,v 1.7 2006/08/18 14:51:00 acolyer Exp $ + * @version $Id: ClassLoaderRepository.java,v 1.8 2006/08/21 15:23:58 aclement Exp $ * @author M. Dahm * @author David Dixon-Peugh */ public class ClassLoaderRepository implements Repository { private static java.lang.ClassLoader bootClassLoader = null; private java.lang.ClassLoader loader; - private WeakHashMap /**/loadedClassesLocalCache = new WeakHashMap(); - private static Map /**/loadedUrlsSharedCache = new HashMap(); - public static boolean useSharedCache = true; - private static long timeManipulatingURLs = 0L; - private static long timeSpentLoading = 0L; - private static int classesLoadedCount = 0; + // Choice of cache... + private WeakHashMap /**/localCache = new WeakHashMap(); + private static SoftHashMap /**/sharedCache = new SoftHashMap(Collections.synchronizedMap(new HashMap())); + + // For fast translation of the classname *intentionally not static* + private SoftHashMap /**/ nameMap = new SoftHashMap(new HashMap(), false); + + public static boolean useSharedCache = + System.getProperty("org.aspectj.apache.bcel.useSharedCache","true").equalsIgnoreCase("true"); + private static int cacheHitsShared = 0; private static int missSharedEvicted = 0; // Misses in shared cache access due to reference GC - private static int misses = 0; + private long timeManipulatingURLs = 0L; + private long timeSpentLoading = 0L; + private int classesLoadedCount = 0; + private int misses = 0; private int cacheHitsLocal = 0; private int missLocalEvicted = 0; // Misses in local cache access due to reference GC - static { - useSharedCache = System.getProperty("org.aspectj.apache.bcel.useSharedCache","true").equalsIgnoreCase("true"); - } - public ClassLoaderRepository( java.lang.ClassLoader loader ) { - this.loader = (loader != null) ? loader : getBootClassLoader(); + this.loader = (loader != null) ? loader : getBootClassLoader(); } private static synchronized java.lang.ClassLoader getBootClassLoader() { @@ -110,43 +117,113 @@ public class ClassLoaderRepository implements Repository { } return bootClassLoader; } + + // Can track back to its key + public static class SoftHashMap extends AbstractMap { + private Map map; + boolean recordMiss = true; // only interested in recording miss stats sometimes + private ReferenceQueue rq = new ReferenceQueue(); + + public SoftHashMap(Map map) { this.map = map; } + public SoftHashMap() { this(new HashMap()); } + public SoftHashMap(Map map, boolean b) { this(map); this.recordMiss=b;} + + class SpecialValue extends SoftReference { + private final Object key; + SpecialValue(Object k,Object v) { + super(v,rq); + this.key = k; + } + } + + private void processQueue() { + SpecialValue sv = null; + while ((sv = (SpecialValue)rq.poll())!=null) { + map.remove(sv.key); + } + } + + public Object get(Object key) { + SpecialValue value = (SpecialValue)map.get(key); + if (value==null) return null; + if (value.get()==null) { + // it got GC'd + map.remove(value.key); + if (recordMiss) missSharedEvicted++; + return null; + } else { + return value.get(); + } + } + + public Object put(Object k, Object v) { + processQueue(); + return map.put(k, new SpecialValue(k,v)); + } + + public Set entrySet() { + return map.entrySet(); + } + + public void clear() { + processQueue(); + map.clear(); + } + + public int size() { + processQueue(); + return map.size(); + } + + public Object remove(Object k) { + processQueue(); + SpecialValue value = (SpecialValue)map.remove(k); + if (value==null) return null; + if (value.get()!=null) { + return value.get(); + } + return null; + } + } /** * Store a new JavaClass into this repository as a soft reference and return the reference */ - private Reference storeClassAsReference( JavaClass clazz ) { - Reference ref = new SoftReference(clazz); - loadedClassesLocalCache.put( clazz.getClassName(), ref); - clazz.setRepository( this ); - return ref; + private void storeClassAsReference(URL url, JavaClass clazz ) { + if (useSharedCache) { + clazz.setRepository(null); // can't risk setting repository, we'll get in a pickle! + sharedCache.put(url, clazz); + } else { + clazz.setRepository(this); + localCache.put(url, new SoftReference(clazz)); + } } - /** - * Store a reference in the shared cache - */ - private void storeReferenceShared(URL url, Reference ref) { - if (useSharedCache) loadedUrlsSharedCache.put(url, ref); - } - /** * Store a new JavaClass into this Repository. */ public void storeClass( JavaClass clazz ) { - storeClassAsReference(clazz); + storeClassAsReference(toURL(clazz.getClassName()),clazz); } /** * Remove class from repository */ public void removeClass(JavaClass clazz) { - loadedClassesLocalCache.remove(clazz.getClassName()); + if (useSharedCache) sharedCache.remove(toURL(clazz.getClassName())); + else localCache.remove(toURL(clazz.getClassName())); } /** * Find an already defined JavaClass in the local cache. */ public JavaClass findClass( String className ) { - Object o = loadedClassesLocalCache.get( className ); + if (useSharedCache) return findClassShared(toURL(className)); + else return findClassLocal(toURL(className)); + } + + private JavaClass findClassLocal( URL url ) { + Object o = localCache.get(url); if (o != null) { o = ((Reference)o).get(); if (o != null) { @@ -162,55 +239,56 @@ public class ClassLoaderRepository implements Repository { * Find an already defined JavaClass in the shared cache. */ private JavaClass findClassShared(URL url) { - if (!useSharedCache) return null; - Object o = (Reference)loadedUrlsSharedCache.get(url); - if (o != null) { - o = ((Reference)o).get(); - if (o != null) { - return (JavaClass)o; - } else { - missSharedEvicted++; - } - } - return null; + return (JavaClass)sharedCache.get(url); } + private URL toURL(String className) { + URL url = (URL)nameMap.get(className); + if (url==null) { + String classFile = className.replace('.', '/'); + url = loader.getResource( classFile + ".class" ); + nameMap.put(className, url); + } + return url; + } /** * Lookup a JavaClass object from the Class Name provided. */ public JavaClass loadClass( String className ) throws ClassNotFoundException { - String classFile = className.replace('.', '/'); - - // Check the local cache - JavaClass clazz = findClass(className); - if (clazz != null) { cacheHitsLocal++; return clazz; } + + // translate to a URL + long time = System.currentTimeMillis(); + java.net.URL url = toURL(className); + timeManipulatingURLs += (System.currentTimeMillis() - time); + if (url==null) throw new ClassNotFoundException(className + " not found."); + + JavaClass clazz = null; - try { - // Work out the URL - long time = System.currentTimeMillis(); - java.net.URL url = (useSharedCache?loader.getResource( classFile + ".class" ):null); - if (useSharedCache && url==null) throw new ClassNotFoundException(className + " not found."); - InputStream is = (useSharedCache?url.openStream():loader.getResourceAsStream( classFile + ".class" )); - timeManipulatingURLs += (System.currentTimeMillis() - time); - - // Check the shared cache + // Look in the appropriate cache + if (useSharedCache) { clazz = findClassShared(url); - if (clazz != null) { cacheHitsShared++; timeSpentLoading+=(System.currentTimeMillis() - time); return clazz; } + if (clazz != null) { cacheHitsShared++; return clazz; } + } else { + clazz = findClassLocal(url); + if (clazz != null) { cacheHitsLocal++; return clazz; } + } + + // Didn't find it in either cache + misses++; - // Didn't find it in either cache - misses++; - - if (is == null) { // TODO move this up? - throw new ClassNotFoundException(className + " not found."); - } - - ClassParser parser = new ClassParser( is, className ); + try { + // Load it + String classFile = className.replace('.', '/'); + InputStream is = (useSharedCache?url.openStream():loader.getResourceAsStream( classFile + ".class" )); + if (is == null) { + throw new ClassNotFoundException(className + " not found."); + } + ClassParser parser = new ClassParser( is, className ); clazz = parser.parse(); - // Store it in both caches - Reference ref = storeClassAsReference( clazz ); - storeReferenceShared(url,ref); + // Cache it + storeClassAsReference(url, clazz ); timeSpentLoading += (System.currentTimeMillis() - time); classesLoadedCount++; @@ -221,27 +299,42 @@ public class ClassLoaderRepository implements Repository { } -/** + /** * Produce a report on cache usage. */ - public String reportAllStatistics() { + public String report() { StringBuffer sb = new StringBuffer(); sb.append("BCEL repository report."); - if (!useSharedCache) sb.append(" (Shared cache deactivated)"); + if (useSharedCache) sb.append(" (shared cache)"); + else sb.append(" (local cache)"); sb.append(" Total time spent loading: "+timeSpentLoading+"ms."); - sb.append(" Time manipulating URLs: "+timeManipulatingURLs+"ms."); + sb.append(" Time spent manipulating URLs: "+timeManipulatingURLs+"ms."); sb.append(" Classes loaded: "+classesLoadedCount+"."); - if (useSharedCache) sb.append(" URL cache (hits/missDueToEviction): ("+cacheHitsShared+"/"+missSharedEvicted+")."); - sb.append(" Local cache (hits/missDueToEviction): ("+cacheHitsLocal+"/"+missLocalEvicted+")."); + if (useSharedCache) { + sb.append(" Shared cache size: "+sharedCache.size()); + sb.append(" Shared cache (hits/missDueToEviction): ("+cacheHitsShared+"/"+missSharedEvicted+")."); + } else { + sb.append(" Local cache size: "+localCache.size()); + sb.append(" Local cache (hits/missDueToEviction): ("+cacheHitsLocal+"/"+missLocalEvicted+")."); + } return sb.toString(); } - public int reportLocalCacheHits() { - return cacheHitsLocal; - } - - public static int reportSharedCacheHits() { - return cacheHitsShared; + /** + * Returns an array of the stats, for testing, the order is fixed: + * 0=time spent loading (static) + * 1=time spent manipulating URLs (static) + * 2=classes loaded (static) + * 3=cache hits shared (static) + * 4=misses in shared due to eviction (static) + * 5=cache hits local + * 6=misses in local due to eviction + * 7=shared cache size + */ + public long[] reportStats() { + return new long[]{timeSpentLoading,timeManipulatingURLs,classesLoadedCount, + cacheHitsShared,missSharedEvicted,cacheHitsLocal,missLocalEvicted, + sharedCache.size()}; } /** @@ -257,7 +350,6 @@ public class ClassLoaderRepository implements Repository { missLocalEvicted = 0; misses = 0; clear(); - clearShared(); } @@ -267,12 +359,10 @@ public class ClassLoaderRepository implements Repository { /** Clear all entries from the local cache */ public void clear() { - loadedClassesLocalCache.clear(); - } - - /** Clear all entries from the shared cache */ - public static void clearShared() { - loadedUrlsSharedCache.clear(); + if (useSharedCache) sharedCache.clear(); + else localCache.clear(); } + } + diff --git a/bcel-builder/testsrc/org/aspectj/apache/bcel/classfile/tests/ClassloaderRepositoryTest.java b/bcel-builder/testsrc/org/aspectj/apache/bcel/classfile/tests/ClassloaderRepositoryTest.java index d9e0d6d58..7202be3fa 100644 --- a/bcel-builder/testsrc/org/aspectj/apache/bcel/classfile/tests/ClassloaderRepositoryTest.java +++ b/bcel-builder/testsrc/org/aspectj/apache/bcel/classfile/tests/ClassloaderRepositoryTest.java @@ -26,20 +26,33 @@ public class ClassloaderRepositoryTest extends TestCase { // Retrieve string 5 times from same repository, 4 hits should be from local cache public void testLocalCacheWorks() throws ClassNotFoundException { + ClassLoaderRepository.useSharedCache=false; JavaClass jc = rep1.loadClass("java.lang.String"); jc = rep1.loadClass("java.lang.String"); jc = rep1.loadClass("java.lang.String"); jc = rep1.loadClass("java.lang.String"); jc = rep1.loadClass("java.lang.String"); - assertTrue("Should have used local cache 4 times: "+rep1.reportLocalCacheHits(),rep1.reportLocalCacheHits()==4); + assertTrue("Should have used local cache 4 times: "+reportLocalCacheHits(rep1),reportLocalCacheHits(rep1)==4); + } + + // Retrieve string 5 times from same repository, 4 hits should be from local cache + public void testSharedCacheWorksOnOne() throws ClassNotFoundException { + ClassLoaderRepository.useSharedCache=true; + JavaClass jc = rep1.loadClass("java.lang.String"); + jc = rep1.loadClass("java.lang.String"); + jc = rep1.loadClass("java.lang.String"); + jc = rep1.loadClass("java.lang.String"); + jc = rep1.loadClass("java.lang.String"); + assertTrue("Should have used local cache 4 times: "+reportSharedCacheHits(rep1),reportSharedCacheHits(rep1)==4); } // Retrieve String through one repository then load again through another, should be shared cache hit public void testSharedCacheWorks() throws ClassNotFoundException { + ClassLoaderRepository.useSharedCache=true; JavaClass jc = rep1.loadClass("java.lang.String"); jc = rep2.loadClass("java.lang.String"); - assertTrue("Should have retrieved String from shared cache: "+ClassLoaderRepository.reportSharedCacheHits(), - ClassLoaderRepository.reportSharedCacheHits()==1); + assertTrue("Should have retrieved String from shared cache: "+reportSharedCacheHits(rep1), + reportSharedCacheHits(rep1)==1); } // Shared cache OFF, shouldn't get a shared cache hit @@ -49,8 +62,8 @@ public class ClassloaderRepositoryTest extends TestCase { JavaClass jc = rep1.loadClass("java.lang.String"); jc = rep2.loadClass("java.lang.String"); assertTrue("Should not have retrieved String from shared cache: "+ - ClassLoaderRepository.reportSharedCacheHits(), - ClassLoaderRepository.reportSharedCacheHits()==0); + reportSharedCacheHits(rep1), + reportSharedCacheHits(rep1)==0); } finally { ClassLoaderRepository.useSharedCache=true; } @@ -58,10 +71,19 @@ public class ClassloaderRepositoryTest extends TestCase { public void tearDown() throws Exception { super.tearDown(); - System.err.println("Rep1: "+rep1.reportAllStatistics()); - System.err.println("Rep2: "+rep2.reportAllStatistics()); + System.err.println("Rep1: "+rep1.reportStats()); + System.err.println("Rep2: "+rep2.reportStats()); rep1.reset(); rep2.reset(); } - + + private long reportLocalCacheHits(ClassLoaderRepository rep) { + return rep.reportStats()[5]; + } + + private long reportSharedCacheHits(ClassLoaderRepository rep) { + return rep.reportStats()[3]; + } + } + diff --git a/lib/bcel/bcel-src.zip b/lib/bcel/bcel-src.zip index 1d7d84805..c1115a886 100644 Binary files a/lib/bcel/bcel-src.zip and b/lib/bcel/bcel-src.zip differ diff --git a/lib/bcel/bcel.jar b/lib/bcel/bcel.jar index 493ffe6a2..7df53e540 100644 Binary files a/lib/bcel/bcel.jar and b/lib/bcel/bcel.jar differ diff --git a/weaver/src/org/aspectj/weaver/bcel/BcelWeaver.java b/weaver/src/org/aspectj/weaver/bcel/BcelWeaver.java index 6a51fd484..38a56ef5b 100644 --- a/weaver/src/org/aspectj/weaver/bcel/BcelWeaver.java +++ b/weaver/src/org/aspectj/weaver/bcel/BcelWeaver.java @@ -187,7 +187,12 @@ public class BcelWeaver implements IWeaver { WeaverStateInfo wsi = type.getWeaverState(); if (wsi != null && wsi.isReweavable()) { BcelObjectType classType = getClassType(type.getName()); - classType.setJavaClass(Utility.makeJavaClass(classType.getJavaClass().getFileName(), wsi.getUnwovenClassFileData(classType.getJavaClass().getBytes()))); + JavaClass wovenJavaClass = classType.getJavaClass(); + JavaClass unwovenJavaClass = Utility.makeJavaClass(wovenJavaClass.getFileName(), + wsi.getUnwovenClassFileData(wovenJavaClass.getBytes())); + world.storeClass(unwovenJavaClass); + classType.setJavaClass(unwovenJavaClass); +// classType.setJavaClass(Utility.makeJavaClass(classType.getJavaClass().getFileName(), wsi.getUnwovenClassFileData(classType.getJavaClass().getBytes()))); } //TODO AV - happens to reach that a lot of time: for each type flagged reweavable X for each aspect in the weaverstate diff --git a/weaver/src/org/aspectj/weaver/bcel/BcelWorld.java b/weaver/src/org/aspectj/weaver/bcel/BcelWorld.java index b0d5ce96a..2e13c5d54 100644 --- a/weaver/src/org/aspectj/weaver/bcel/BcelWorld.java +++ b/weaver/src/org/aspectj/weaver/bcel/BcelWorld.java @@ -81,7 +81,7 @@ import org.aspectj.weaver.patterns.SimpleScope; public class BcelWorld extends World implements Repository { private ClassPathManager classPath; - private Repository delegate; + protected Repository delegate; //private ClassPathManager aspectPath = null; @@ -644,7 +644,7 @@ public class BcelWorld extends World implements Repository { } public void storeClass(JavaClass clazz) { - throw new RuntimeException("Not implemented"); + // doesn't need to do anything } public void removeClass(JavaClass clazz) { diff --git a/weaver/src/org/aspectj/weaver/ltw/LTWWorld.java b/weaver/src/org/aspectj/weaver/ltw/LTWWorld.java index ff6b7b727..a89dc6f77 100644 --- a/weaver/src/org/aspectj/weaver/ltw/LTWWorld.java +++ b/weaver/src/org/aspectj/weaver/ltw/LTWWorld.java @@ -19,6 +19,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.aspectj.apache.bcel.classfile.JavaClass; import org.aspectj.bridge.IMessageHandler; import org.aspectj.util.LangUtil; import org.aspectj.weaver.ICrossReferenceHandler; @@ -48,7 +49,6 @@ import org.aspectj.weaver.reflect.ReflectionWorld; */ public class LTWWorld extends BcelWorld implements IReflectionWorld { - private AnnotationFinder annotationFinder; private ClassLoader loader; // weavingContext? private IWeavingContext weavingContext; @@ -244,5 +244,10 @@ public class LTWWorld extends BcelWorld implements IReflectionWorld { ((ReferenceType)ret).setDelegate(rtd); return ret; } + + public void storeClass(JavaClass clazz) { + delegate.storeClass(clazz); + } + } diff --git a/weaver/testsrc/org/aspectj/weaver/bcel/ClassLoaderRepositoryTests.java b/weaver/testsrc/org/aspectj/weaver/bcel/ClassLoaderRepositoryTests.java index c60f43d57..a6f9b0000 100644 --- a/weaver/testsrc/org/aspectj/weaver/bcel/ClassLoaderRepositoryTests.java +++ b/weaver/testsrc/org/aspectj/weaver/bcel/ClassLoaderRepositoryTests.java @@ -14,10 +14,12 @@ package org.aspectj.weaver.bcel; import java.io.File; -import java.io.IOException; +import java.lang.ref.Reference; +import java.lang.reflect.Field; import java.net.URL; import java.net.URLClassLoader; import java.util.Enumeration; +import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @@ -27,43 +29,185 @@ import org.aspectj.apache.bcel.util.ClassLoaderRepository; /** NOT YET INCLUDED IN A FULL TEST RUN - WORK IN PROGRESS CHECKING CLASSLOADERREPOSITORY OPTIMIZATIONS */ public class ClassLoaderRepositoryTests extends TestCase { - - public void testRepositorySharing() throws Exception { - ClassLoaderRepository.useSharedCache=false; - File f = new File("../lib/aspectj/lib/aspectjtools.jar"); + private File f; + private ZipFile zf; + private Enumeration entries; + private Map map; + + public void setUp() throws Exception { + f = new File("../lib/aspectj/lib/aspectjtools.jar"); + assertTrue("Couldn't find aspectjtools to test. Tried: "+f.getAbsolutePath(),f.exists()); + zf = new ZipFile(f); + entries = zf.entries(); +// ClassLoaderRepository.sharedCacheCompactFrequency = 16384; + map = getSharedMap(); + } + + public void tearDown() { + new ClassLoaderRepository(null).reset(); + } + + private ClassLoaderRepository setupRepository() throws Exception { ClassLoader cl = Thread.currentThread().getContextClassLoader(); - ClassLoader cl1 = new URLClassLoader(new URL[]{f.toURL()},cl); - ClassLoader cl2 = new URLClassLoader(new URL[]{f.toURL()},cl); - ClassLoaderRepository rep1 = new ClassLoaderRepository(cl1); - ClassLoaderRepository rep2 = new ClassLoaderRepository(cl2); - try { - assertTrue("Couldnt find aspectjtools to test. Tried: "+f.getAbsolutePath(),f.exists()); - ZipFile zf = new ZipFile(f); - int i = 0; - Enumeration entries = zf.entries(); - while (entries.hasMoreElements()) { - ZipEntry zfe = (ZipEntry)entries.nextElement(); - String classfileName = zfe.getName(); - if (classfileName.endsWith(".class")) { - String clazzname = classfileName.substring(0,classfileName.length()-6).replace('/','.'); - - // twice by each - rep1.loadClass(clazzname); - rep1.loadClass(clazzname); - rep2.loadClass(clazzname); - rep2.loadClass(clazzname); - i++; - } + ClassLoader res = new URLClassLoader(new URL[]{f.toURL()},cl); + ClassLoaderRepository rep = new ClassLoaderRepository(res); + return rep; + } + + private void compareTwoRepositories() throws Exception { + ClassLoaderRepository rep1 = setupRepository(); + ClassLoaderRepository rep2 = setupRepository(); + int i = 0; + while (entries.hasMoreElements()) { + ZipEntry zfe = (ZipEntry)entries.nextElement(); + String classfileName = zfe.getName(); + if (classfileName.endsWith(".class")) { + String clazzname = classfileName.substring(0,classfileName.length()-6).replace('/','.'); + + // twice by each + rep1.loadClass(clazzname); + rep1.loadClass(clazzname); + rep2.loadClass(clazzname); + rep2.loadClass(clazzname); + i++; + } + } + System.err.println("Successfully compared "+i+" entries!!"); + System.err.println(rep1.report()); + System.err.println(rep2.report()); + } + + private void loadOnce() throws Exception { + ClassLoaderRepository rep = setupRepository(); + while (entries.hasMoreElements()) { + ZipEntry zfe = (ZipEntry) entries.nextElement(); + String classfileName = zfe.getName(); + if (classfileName.endsWith(".class")) { + String clazzname = classfileName.substring(0, + classfileName.length() - 6).replace('/', '.'); + + rep.loadClass(clazzname); + } + } + } + + public void testMultiThreaded() throws Throwable { + ClassLoaderRepository.useSharedCache=true; +// ClassLoaderRepository.sharedCacheCompactFrequency = 200; + //loadOnce(); + TestThread threads[] = new TestThread[6]; + for (int i=0; i