Skip to content

Commit

Permalink
Implement concurrent cache of generated classes, improve performance …
Browse files Browse the repository at this point in the history
…of Enhancer
  • Loading branch information
vlsi committed Nov 16, 2015
1 parent b4cd1d6 commit 05f830f
Show file tree
Hide file tree
Showing 6 changed files with 449 additions and 116 deletions.
200 changes: 133 additions & 67 deletions cglib/src/main/java/net/sf/cglib/core/AbstractClassGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,38 @@
*/
package net.sf.cglib.core;

import java.io.*;
import java.util.*;
import java.lang.ref.*;
import java.security.ProtectionDomain;
import net.sf.cglib.core.internal.Function;
import net.sf.cglib.core.internal.LoadingCache;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Type;

import java.lang.ref.WeakReference;
import java.security.ProtectionDomain;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
* Abstract class for all code-generating CGLIB utilities.
* In addition to caching generated classes for performance, it provides hooks for
* customizing the <code>ClassLoader</code>, name of the generated class, and transformations
* applied before generation.
*/
abstract public class AbstractClassGenerator
abstract public class AbstractClassGenerator<T>
implements ClassGenerator
{
private static final Object NAME_KEY = new Object();
private static final ThreadLocal CURRENT = new ThreadLocal();

private static volatile Map<ClassLoader, ClassLoaderData> CACHE = new WeakHashMap<ClassLoader, ClassLoaderData>();
// private static final WeakLoadingCache<ClassLoader, ClassLoaderData> CACHE =
// new WeakLoadingCache<ClassLoader, ClassLoaderData>(
// new Function<ClassLoader, ClassLoaderData>() {
// public ClassLoaderData apply(ClassLoader key) {
// return new ClassLoaderData(key);
// }
// }
// );

private GeneratorStrategy strategy = DefaultGeneratorStrategy.INSTANCE;
private NamingPolicy namingPolicy = DefaultNamingPolicy.INSTANCE;
private Source source;
Expand All @@ -46,9 +57,63 @@ abstract public class AbstractClassGenerator
private String className;
private boolean attemptLoad;

protected static class ClassLoaderData {
private final ConcurrentMap<String, Boolean> reservedClassNames = new ConcurrentHashMap<String, Boolean>(1, 0.75f, 1);
private final LoadingCache<AbstractClassGenerator, Object, Object> generatedClasses;
private final WeakReference<ClassLoader> classLoader;

private final Predicate uniqueNamePredicate = new Predicate() {
public boolean evaluate(Object arg) {
return allocateName((String) arg);
}
};

private static final Function<AbstractClassGenerator, Object> GET_KEY = new Function<AbstractClassGenerator, Object>() {
public Object apply(AbstractClassGenerator gen) {
return gen.key;
}
};

public ClassLoaderData(ClassLoader classLoader) {
this.classLoader = new WeakReference<ClassLoader>(classLoader);
Function<AbstractClassGenerator, Object> load =
new Function<AbstractClassGenerator, Object>() {
public Object apply(AbstractClassGenerator gen) {
Class klass = gen.generate(ClassLoaderData.this);
return gen.wrapCachedClass(klass);
}
};
generatedClasses = new LoadingCache<AbstractClassGenerator, Object, Object>(GET_KEY, load);
}

public ClassLoader getClassLoader() {
return classLoader.get();
}

public boolean allocateName(String name) {
return reservedClassNames.putIfAbsent(name, true) != null;
}

public Predicate getUniqueNamePredicate() {
return uniqueNamePredicate;
}

public Object get(AbstractClassGenerator gen) {
Object cachedValue = generatedClasses.get(gen);
return gen.unwrapCachedValue(cachedValue);
}
}

protected T wrapCachedClass(Class klass) {
return (T) new WeakReference(klass);
}

protected Object unwrapCachedValue(T cached) {
return ((WeakReference) cached).get();
}

protected static class Source {
String name;
Map cache = new WeakHashMap();
public Source(String name) {
this.name = name;
}
Expand All @@ -63,22 +128,15 @@ protected void setNamePrefix(String namePrefix) {
}

final protected String getClassName() {
if (className == null)
className = getClassName(getClassLoader());
return className;
}

private String getClassName(final ClassLoader loader) {
final Set nameCache = getClassNameCache(loader);
return namingPolicy.getClassName(namePrefix, source.name, key, new Predicate() {
public boolean evaluate(Object arg) {
return nameCache.contains(arg);
}
});
private void setClassName(String className) {
this.className = className;
}

private Set getClassNameCache(ClassLoader loader) {
return (Set)((Map)source.cache.get(loader)).get(NAME_KEY);
private String generateClassName(Predicate nameTestPredicate) {
return namingPolicy.getClassName(namePrefix, source.name, key, nameTestPredicate);
}

/**
Expand Down Expand Up @@ -199,61 +257,69 @@ protected ProtectionDomain getProtectionDomain() {

protected Object create(Object key) {
try {
Class gen = null;

synchronized (source) {
ClassLoader loader = getClassLoader();
ProtectionDomain protectionDomain = getProtectionDomain();
Map cache2 = null;
cache2 = (Map)source.cache.get(loader);
if (cache2 == null) {
cache2 = new HashMap();
cache2.put(NAME_KEY, new HashSet());
source.cache.put(loader, cache2);
} else if (useCache) {
Reference ref = (Reference)cache2.get(key);
gen = (Class) (( ref == null ) ? null : ref.get());
}
if (gen == null) {
Object save = CURRENT.get();
CURRENT.set(this);
try {
this.key = key;

if (attemptLoad) {
try {
gen = loader.loadClass(getClassName());
} catch (ClassNotFoundException e) {
// ignore
}
}
if (gen == null) {
byte[] b = strategy.generate(this);
String className = ClassNameReader.getClassName(new ClassReader(b));
getClassNameCache(loader).add(className);
if(protectionDomain == null) {
gen = ReflectUtils.defineClass(className, b, loader);
} else {
gen = ReflectUtils.defineClass(className, b, loader, protectionDomain);
}
}

if (useCache) {
cache2.put(key, new WeakReference(gen));
}
return firstInstance(gen);
} finally {
CURRENT.set(save);
ClassLoader loader = getClassLoader();
Map<ClassLoader, ClassLoaderData> cache = CACHE;
ClassLoaderData data = cache.get(loader);
if (data == null) {
synchronized (AbstractClassGenerator.class) {
data = cache.get(loader);
if (data == null) {
Map<ClassLoader, ClassLoaderData> newCache = new WeakHashMap<ClassLoader, ClassLoaderData>(cache);
data = new ClassLoaderData(classLoader);
newCache.put(classLoader, data);
CACHE = newCache;
}
}
}
return firstInstance(gen);
this.key = key;
Object obj = data.get(this);
if (obj instanceof Class) {
return firstInstance((Class) obj);
}
return nextInstance(obj);
} catch (RuntimeException e) {
throw e;
} catch (Error e) {
throw e;
} catch (Exception e) {
throw new CodeGenerationException(e);
}
}

protected Class generate(ClassLoaderData data) {
Class gen;
Object save = CURRENT.get();
CURRENT.set(this);
try {
ClassLoader classLoader = data.getClassLoader();
this.setClassName(generateClassName(data.getUniqueNamePredicate()));
if (attemptLoad) {
try {
gen = classLoader.loadClass(getClassName());
return gen;
} catch (ClassNotFoundException e) {
// ignore
}
}
byte[] b = strategy.generate(this);
String className = ClassNameReader.getClassName(new ClassReader(b));
ProtectionDomain protectionDomain = getProtectionDomain();
synchronized (classLoader) { // just in case
if (protectionDomain == null) {
gen = ReflectUtils.defineClass(className, b, classLoader);
} else {
gen = ReflectUtils.defineClass(className, b, classLoader, protectionDomain);
}
}
return gen;
} catch (RuntimeException e) {
throw e;
} catch (Error e) {
throw e;
} catch (Exception e) {
throw new CodeGenerationException(e);
} finally {
CURRENT.set(save);
}
}

Expand Down
5 changes: 5 additions & 0 deletions cglib/src/main/java/net/sf/cglib/core/internal/Function.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package net.sf.cglib.core.internal;

public interface Function<K, V> {
V apply(K key);
}
86 changes: 86 additions & 0 deletions cglib/src/main/java/net/sf/cglib/core/internal/LoadingCache.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package net.sf.cglib.core.internal;

import java.util.concurrent.*;

public class LoadingCache<K, KK, V> {
protected final ConcurrentMap<KK, Object> map;
protected final Function<K, V> loader;
protected final Function<K, KK> keyMapper;

public static final Function IDENTITY = new Function() {
public Object apply(Object key) {
return key;
}
};

public LoadingCache(Function<K, KK> keyMapper, Function<K, V> loader) {
this.keyMapper = keyMapper;
this.loader = loader;
this.map = new ConcurrentHashMap<KK, Object>();
}

@SuppressWarnings("unchecked")
public static <K> Function<K, K> identity() {
return IDENTITY;
}

public V get(K key) {
final KK cacheKey = keyMapper.apply(key);
Object v = map.get(cacheKey);
if (v != null && !(v instanceof FutureTask)) {
return (V) v;
}

return createEntry(key, cacheKey, v);
}

/**
* Loads entry to the cache.
* If entry is missing, put {@link FutureTask} first so other competing thread might wait for the result.
* @param key original key that would be used to load the instance
* @param cacheKey key that would be used to store the entry in internal map
* @param v null or {@link FutureTask<V>}
* @return newly created instance
*/
protected V createEntry(final K key, KK cacheKey, Object v) {
FutureTask<V> task;
boolean creator = false;
if (v != null) {
// Another thread is already loading an instance
task = (FutureTask<V>) v;
} else {
task = new FutureTask<V>(new Callable<V>() {
public V call() throws Exception {
return loader.apply(key);
}
});
Object prevTask = map.putIfAbsent(cacheKey, task);
if (prevTask == null) {
// creator does the load
creator = true;
task.run();
} else if (prevTask instanceof FutureTask) {
task = (FutureTask<V>) prevTask;
} else {
return (V) prevTask;
}
}

V result;
try {
result = task.get();
} catch (InterruptedException e) {
throw new IllegalStateException("Interrupted while loading cache item", e);
} catch (ExecutionException e) {
Throwable cause = e.getCause();
if (cause instanceof RuntimeException) {
throw ((RuntimeException) cause);
}
throw new IllegalStateException("Unable to load cache item", cause);
}
if (creator) {
map.put(cacheKey, result);
}
return result;
}
}

0 comments on commit 05f830f

Please sign in to comment.