Stuart McCulloch contributed a custom classloader that fixes the memory leak and supports OSGi-style classloaders (plus any other form of non-delegation).
Now when Guice loads its own classes (for AOP, etc.), those classes will get garbage collected properly when they go out of scope.
git-svn-id: https://google-guice.googlecode.com/svn/trunk@538 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/build.xml b/build.xml
index 28620b7..e9dcaff 100644
--- a/build.xml
+++ b/build.xml
@@ -21,9 +21,9 @@
<zipfileset src="lib/build/asm-3.1.jar"/>
<zipfileset src="lib/build/google-collect-snapshot-20080530.jar"/>
<rule pattern="com.google.common.**" result="com.google.inject.internal.@1"/>
- <rule pattern="net.sf.cglib.**" result="com.google.inject.cglib.@1"/>
- <rule pattern="org.objectweb.asm.**" result="com.google.inject.asm.@1"/>
- <keep pattern="com.google.**"/>
+ <rule pattern="net.sf.cglib.**" result="com.google.inject.internal.cglib.@1"/>
+ <rule pattern="org.objectweb.asm.**" result="com.google.inject.internal.asm.@1"/>
+ <keep pattern="com.google.inject.**"/>
</jarjar>
</target>
@@ -89,8 +89,12 @@
<pathelement location="lib/build/junit.jar"/>
<pathelement location="lib/build/servlet-api-2.5.jar"/>
<pathelement location="lib/build/easymock.jar"/>
+ <pathelement location="lib/build/google-collect-snapshot-20080530.jar"/>
</classpath>
<arg value="com.google.inject.AllTests"/>
+ <syspropertyset>
+ <propertyref name="guice.custom.loader"/>
+ </syspropertyset>
</java>
</target>
diff --git a/common.xml b/common.xml
index 402e54d..cc7287c 100644
--- a/common.xml
+++ b/common.xml
@@ -41,7 +41,7 @@
<property name="exclude.imports" value=""/>
<property name="guice.imports" value="com.google.inject.*;version=${api.version}"/>
<property name="Import-Package" value="${exclude.imports},${guice.imports},*;resolution:=optional"/>
- <property name="Export-Package" value="${module}.*;version=${api.version}"/>
+ <property name="Export-Package" value="!${module}.internal.*,${module}.*;version=${api.version}"/>
<property name="-nouses" value="true"/>
<property name="-removeheaders" value="Bnd-LastModified,Ignore-Package,Include-Resource,Private-Package,Tool"/>
diff --git a/guice.iml b/guice.iml
index f4d0954..cb08b03 100644
--- a/guice.iml
+++ b/guice.iml
@@ -72,7 +72,9 @@
<root url="file://$MODULE_DIR$/lib/build" />
</CLASSES>
<JAVADOC />
- <SOURCES />
+ <SOURCES>
+ <root url="jar://$MODULE_DIR$/lib/build/cglib-src-2.2.jar!/src/proxy" />
+ </SOURCES>
<jarDirectory url="file://$MODULE_DIR$/lib/build" recursive="false" />
</library>
</orderEntry>
diff --git a/src/com/google/inject/ConstructionContext.java b/src/com/google/inject/ConstructionContext.java
index 55c2b45..bbcb335 100644
--- a/src/com/google/inject/ConstructionContext.java
+++ b/src/com/google/inject/ConstructionContext.java
@@ -16,6 +16,7 @@
package com.google.inject;
+import com.google.inject.internal.BytecodeGen;
import com.google.inject.internal.Errors;
import com.google.inject.internal.ErrorsException;
import java.lang.reflect.InvocationHandler;
@@ -79,9 +80,9 @@
= new DelegatingInvocationHandler<T>();
invocationHandlers.add(invocationHandler);
- Object object = Proxy.newProxyInstance(expectedType.getClassLoader(),
- new Class[] { expectedType }, invocationHandler);
- return expectedType.cast(object);
+ ClassLoader classLoader = BytecodeGen.getClassLoader(expectedType);
+ return expectedType.cast(Proxy.newProxyInstance(classLoader,
+ new Class[] { expectedType }, invocationHandler));
}
void setProxyDelegates(T delegate) {
diff --git a/src/com/google/inject/DefaultConstructionProxyFactory.java b/src/com/google/inject/DefaultConstructionProxyFactory.java
index 33bfdc8..ace1f93 100644
--- a/src/com/google/inject/DefaultConstructionProxyFactory.java
+++ b/src/com/google/inject/DefaultConstructionProxyFactory.java
@@ -16,9 +16,9 @@
package com.google.inject;
+import static com.google.inject.internal.BytecodeGen.newFastClass;
import com.google.inject.internal.Errors;
import com.google.inject.internal.ErrorsException;
-import com.google.inject.internal.GuiceFastClass;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
@@ -67,7 +67,7 @@
return new ConstructionProxy<T>() {
Class<T> classToConstruct = constructor.getDeclaringClass();
- FastClass fastClass = GuiceFastClass.create(classToConstruct);
+ FastClass fastClass = newFastClass(classToConstruct);
final FastConstructor fastConstructor = fastClass.getConstructor(constructor);
@SuppressWarnings("unchecked")
diff --git a/src/com/google/inject/InjectorImpl.java b/src/com/google/inject/InjectorImpl.java
index f2180f0..d337d85 100644
--- a/src/com/google/inject/InjectorImpl.java
+++ b/src/com/google/inject/InjectorImpl.java
@@ -23,10 +23,10 @@
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Sets;
+import static com.google.inject.internal.BytecodeGen.newFastClass;
import com.google.inject.internal.Classes;
import com.google.inject.internal.Errors;
import com.google.inject.internal.ErrorsException;
-import com.google.inject.internal.GuiceFastClass;
import com.google.inject.internal.Keys;
import com.google.inject.internal.MatcherAndConverter;
import com.google.inject.internal.Nullability;
@@ -883,9 +883,8 @@
return method.invoke(target, parameters);
}
};
- }
- else {
- FastClass fastClass = GuiceFastClass.create(method.getDeclaringClass());
+ } else {
+ FastClass fastClass = newFastClass(method.getDeclaringClass());
final FastMethod fastMethod = fastClass.getMethod(method);
methodInvoker = new MethodInvoker() {
@@ -1099,10 +1098,9 @@
reference[0] = new InternalContext(this);
try {
return callable.call(reference[0]);
- }
- finally {
+ } finally {
// Only remove the context if this call created it.
- reference[0] = null;
+ localContext.remove();
}
}
else {
diff --git a/src/com/google/inject/ProxyFactory.java b/src/com/google/inject/ProxyFactory.java
index 48fc94f..13bb646 100644
--- a/src/com/google/inject/ProxyFactory.java
+++ b/src/com/google/inject/ProxyFactory.java
@@ -18,10 +18,10 @@
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+import static com.google.inject.internal.BytecodeGen.newEnhancer;
+import static com.google.inject.internal.BytecodeGen.newFastClass;
import com.google.inject.internal.Errors;
import com.google.inject.internal.ErrorsException;
-import com.google.inject.internal.GuiceFastClass;
-import com.google.inject.internal.GuiceNamingPolicy;
import com.google.inject.internal.ReferenceCache;
import com.google.inject.internal.StackTraceElements;
import java.lang.reflect.Constructor;
@@ -147,17 +147,13 @@
}
// Create the proxied class.
- Enhancer enhancer = new Enhancer();
- enhancer.setSuperclass(declaringClass);
- enhancer.setUseCache(false); // We do enough caching.
+ Enhancer enhancer = newEnhancer(declaringClass);
enhancer.setCallbackFilter(new CallbackFilter() {
public int accept(Method method) {
return indices.get(method);
}
});
enhancer.setCallbackTypes(callbackTypes);
- enhancer.setUseFactory(false);
- enhancer.setNamingPolicy(new GuiceNamingPolicy());
Class<?> proxied = enhancer.createClass();
@@ -172,7 +168,7 @@
*/
private <T> ConstructionProxy<T> createConstructionProxy(Errors errors, final Class<?> clazz,
final Constructor standardConstructor) throws ErrorsException {
- FastClass fastClass = GuiceFastClass.create(clazz);
+ FastClass fastClass = newFastClass(clazz);
final FastConstructor fastConstructor
= fastClass.getConstructor(standardConstructor.getParameterTypes());
final List<Parameter<?>> parameters = Parameter.forConstructor(standardConstructor, errors);
diff --git a/src/com/google/inject/internal/BytecodeGen.java b/src/com/google/inject/internal/BytecodeGen.java
new file mode 100644
index 0000000..47a5ef2
--- /dev/null
+++ b/src/com/google/inject/internal/BytecodeGen.java
@@ -0,0 +1,179 @@
+/**
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.inject.internal;
+
+import static com.google.inject.internal.ReferenceType.WEAK;
+import static java.lang.reflect.Modifier.PROTECTED;
+import static java.lang.reflect.Modifier.PUBLIC;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import net.sf.cglib.core.DefaultNamingPolicy;
+import net.sf.cglib.core.NamingPolicy;
+import net.sf.cglib.proxy.Enhancer;
+import net.sf.cglib.reflect.FastClass;
+
+/**
+ * Utility methods for runtime code generation and class loading. We use this stuff for faster
+ * reflection ({@link FastClass}), method interceptors ({@link Enhancer}) and to proxy circular
+ * dependencies.
+ *
+ * <p>When loading classes, we need to be careful of:
+ * <ul>
+ * <li><strong>Memory leaks.</strong> Generated classes need to be garbage collected in long-lived
+ * applications. Once an injector and any instances it created can be garbage collected, the
+ * corresponding generated classes should be collectable.
+ * <li><strong>Visibility.</strong> Containers like <code>OSGi</code> use class loader boundaries
+ * to enforce modularity at runtime.
+ * </ul>
+ *
+ * <p>For each generated class, there's multiple class loaders involved:
+ * <ul>
+ * <li><strong>The related class's class loader.</strong> Every generated class services exactly
+ * one user-supplied class. This class loader must be used to access members with private and
+ * package visibility.
+ * <li><strong>Guice's class loader.</strong>
+ * <li><strong>Our bridge class loader.</strong> This is a child of the user's class loader. It
+ * selectively delegates to either the user's class loader (for user classes) or the Guice
+ * class loader (for internal classes that are used by the generated classes). This class
+ * loader that owns the classes generated by Guice.
+ * </ul>
+ *
+ * @author mcculls@gmail.com (Stuart McCulloch)
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public final class BytecodeGen {
+
+ static final ClassLoader GUICE_CLASS_LOADER = BytecodeGen.class.getClassLoader();
+
+ /** ie. "com.google.inject.internal" */
+ private static final String GUICE_INTERNAL_PACKAGE
+ = BytecodeGen.class.getName().replaceFirst("\\.internal\\..*$", ".internal");
+
+ /** either "net.sf.cglib", or "com.google.inject.internal.cglib" */
+ private static final String CGLIB_PACKAGE
+ = Enhancer.class.getName().replaceFirst("\\.cglib\\..*$", ".cglib");
+
+ static final NamingPolicy NAMING_POLICY = new DefaultNamingPolicy() {
+ @Override protected String getTag() {
+ return "ByGuice";
+ }
+ };
+
+ /** Use "-Dguice.custom.loader=false" to disable custom classloading. */
+ static final boolean HOOK_ENABLED
+ = "true".equals(System.getProperty("guice.custom.loader", "true"));
+
+ /**
+ * Weak cache of bridge class loaders that make the Guice implementation
+ * classes visible to various code-generated proxies of client classes.
+ */
+ private static final ReferenceCache<ClassLoader, ClassLoader> CLASS_LOADER_CACHE
+ = new ReferenceCache<ClassLoader, ClassLoader>(WEAK, WEAK) {
+ @Override protected ClassLoader create(final ClassLoader typeClassLoader) {
+ return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
+ public ClassLoader run() {
+ return new BridgeClassLoader(typeClassLoader);
+ }
+ });
+ }
+ };
+
+ /**
+ * Returns true if {@code type} can be loaded from our custom class loader.
+ * Private/implementation classes are not visible to classes loaded by other
+ * class loaders, even when they are in the same package. Therefore we cannot
+ * intercept classloading requests for such types.
+ */
+ private static boolean isHookable(Class<?> type) {
+ return (type.getModifiers() & (PROTECTED | PUBLIC)) != 0;
+ }
+
+ /**
+ * For class loaders, {@code null}, is always an alias to the
+ * {@link ClassLoader#getSystemClassLoader() system class loader}.
+ */
+ private static ClassLoader canonicalize(ClassLoader classLoader) {
+ return classLoader != null
+ ? classLoader
+ : ClassLoader.getSystemClassLoader();
+ }
+
+ /**
+ * Returns the class loader to host generated classes for {@code type}.
+ */
+ public static ClassLoader getClassLoader(Class<?> type) {
+ return getClassLoader(type, type.getClassLoader());
+ }
+
+ private static ClassLoader getClassLoader(Class<?> type, ClassLoader delegate) {
+ delegate = canonicalize(delegate);
+
+ if (HOOK_ENABLED && isHookable(type)) {
+ return CLASS_LOADER_CACHE.get(delegate);
+ }
+
+ return delegate;
+ }
+
+ public static FastClass newFastClass(Class<?> type) {
+ FastClass.Generator generator = new FastClass.Generator();
+ generator.setType(type);
+ generator.setClassLoader(getClassLoader(type));
+ generator.setNamingPolicy(NAMING_POLICY);
+ return generator.create();
+ }
+
+ public static Enhancer newEnhancer(Class<?> type) {
+ Enhancer enhancer = new Enhancer();
+ enhancer.setSuperclass(type);
+ enhancer.setUseCache(false); // We do enough caching.
+ enhancer.setUseFactory(false);
+ enhancer.setNamingPolicy(NAMING_POLICY);
+ enhancer.setClassLoader(getClassLoader(type));
+ return enhancer;
+ }
+
+ /**
+ * Loader for Guice-generated classes. For referenced classes, this delegates to either either the
+ * user's classloader (which is the parent of this classloader) or Guice's class loader.
+ */
+ private static class BridgeClassLoader extends ClassLoader {
+
+ public BridgeClassLoader(ClassLoader usersClassLoader) {
+ super(usersClassLoader);
+ }
+
+ @Override protected Class<?> loadClass(String name, boolean resolve)
+ throws ClassNotFoundException {
+
+ // delegate internal requests to Guice class space
+ if (name.startsWith(GUICE_INTERNAL_PACKAGE) || name.startsWith(CGLIB_PACKAGE)) {
+ try {
+ Class<?> clazz = GUICE_CLASS_LOADER.loadClass(name);
+ if (resolve) {
+ resolveClass(clazz);
+ }
+ return clazz;
+ } catch (Exception e) {
+ // fall back to classic delegation
+ }
+ }
+
+ return super.loadClass(name, resolve);
+ }
+ }
+}
diff --git a/src/com/google/inject/internal/GuiceFastClass.java b/src/com/google/inject/internal/GuiceFastClass.java
deleted file mode 100644
index 4495ae8..0000000
--- a/src/com/google/inject/internal/GuiceFastClass.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/**
- * Copyright (C) 2006 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.inject.internal;
-
-import net.sf.cglib.reflect.FastClass;
-
-/**
- * Gives Guice classes custom names.
- *
- * @author crazybob@google.com (Bob Lee)
- */
-public class GuiceFastClass {
-
- public static FastClass create(Class type) {
- return create(type.getClassLoader(), type);
- }
-
- public static FastClass create(ClassLoader loader, Class type) {
- FastClass.Generator generator = new FastClass.Generator();
- generator.setType(type);
- generator.setClassLoader(loader);
- generator.setNamingPolicy(new GuiceNamingPolicy());
- return generator.create();
- }
-}
diff --git a/src/com/google/inject/internal/GuiceNamingPolicy.java b/src/com/google/inject/internal/GuiceNamingPolicy.java
deleted file mode 100644
index c4d38ed..0000000
--- a/src/com/google/inject/internal/GuiceNamingPolicy.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/**
- * Copyright (C) 2006 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.inject.internal;
-
-import net.sf.cglib.core.DefaultNamingPolicy;
-import net.sf.cglib.core.Predicate;
-
-/**
- * Cglib class naming policy for Guice.
- *
- * @author crazybob@google.com (Bob Lee)
- */
-public class GuiceNamingPolicy extends DefaultNamingPolicy {
-
- @Override protected String getTag() {
- return "ByGuice";
- }
-}
diff --git a/test/com/google/inject/AllTests.java b/test/com/google/inject/AllTests.java
index 2c447d4..81added 100644
--- a/test/com/google/inject/AllTests.java
+++ b/test/com/google/inject/AllTests.java
@@ -19,6 +19,7 @@
import com.google.inject.commands.CommandRecorderTest;
import com.google.inject.commands.CommandReplayerTest;
import com.google.inject.commands.CommandRewriteTest;
+import com.google.inject.internal.BytecodeGenTest;
import com.google.inject.internal.FinalizableReferenceQueueTest;
import com.google.inject.internal.LineNumbersTest;
import com.google.inject.internal.ReferenceCacheTest;
@@ -84,6 +85,7 @@
suite.addTestSuite(ReferenceMapTest.class);
suite.addTestSuite(TypesTest.class);
suite.addTestSuite(UniqueAnnotationsTest.class);
+ suite.addTestSuite(BytecodeGenTest.class);
// matcher
suite.addTestSuite(MatcherTest.class);
diff --git a/test/com/google/inject/internal/BytecodeGenTest.java b/test/com/google/inject/internal/BytecodeGenTest.java
new file mode 100644
index 0000000..a930f56
--- /dev/null
+++ b/test/com/google/inject/internal/BytecodeGenTest.java
@@ -0,0 +1,182 @@
+/**
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.inject.internal;
+
+import com.google.inject.Binder;
+import com.google.inject.Guice;
+import com.google.inject.Module;
+import static com.google.inject.matcher.Matchers.any;
+import java.io.File;
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import junit.framework.TestCase;
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+
+/**
+ * @author mcculls@gmail.com (Stuart McCulloch)
+ */
+public class BytecodeGenTest extends TestCase {
+
+ /**
+ * Custom URL classloader with basic visibility rules
+ */
+ static class TestVisibilityClassLoader
+ extends URLClassLoader {
+
+ public TestVisibilityClassLoader() {
+ super(new URL[0]);
+
+ final String[] classpath = System.getProperty("java.class.path").split(File.pathSeparator);
+ for (final String element : classpath) {
+ try {
+ // is it a remote/local URL?
+ addURL(new URL(element));
+ } catch (final MalformedURLException e1) {
+ try {
+ // nope - perhaps it's a filename?
+ addURL(new File(element).toURI().toURL());
+ } catch (final MalformedURLException e2) {
+ throw new RuntimeException(e1);
+ }
+ }
+ }
+ }
+
+ /**
+ * Classic parent-delegating classloaders are meant to override findClass.
+ * However, non-delegating classloaders (as used in OSGi) instead override
+ * loadClass to provide support for "class-space" separation.
+ */
+ @Override
+ protected Class<?> loadClass(final String name, final boolean resolve)
+ throws ClassNotFoundException {
+
+ if (name.startsWith("java.")) {
+
+ // standard bootdelegation of java.*
+ return super.loadClass(name, resolve);
+
+ } else if ((!name.contains(".internal.") && !name.contains(".cglib."))
+ || name.contains("Test")) {
+
+ /*
+ * load public and test classes directly from the classpath - we don't
+ * delegate to our parent because then the loaded classes would also be
+ * able to see private internal Guice classes, as they are also loaded
+ * by the parent classloader.
+ */
+ final Class<?> clazz = findClass(name);
+ if (resolve) {
+ resolveClass(clazz);
+ }
+ return clazz;
+ }
+
+ // hide internal non-test classes
+ throw new ClassNotFoundException();
+ }
+ }
+
+ interface ProxyTest {
+ String sayHello();
+ }
+
+ /**
+ * Note: this class must be marked as public or protected so that the Guice
+ * custom classloader will intercept it. Private and implementation classes
+ * are not intercepted by the custom classloader.
+ *
+ * @see BytecodeGen#isHookable(Class)
+ */
+ protected static class ProxyTestImpl
+ implements ProxyTest {
+
+ public String sayHello() {
+ return "HELLO";
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public void testProxyClassLoading() {
+ final ClassLoader testClassLoader = new TestVisibilityClassLoader();
+
+ final Class<ProxyTest> testAPIClazz;
+ final Class<ProxyTest> testImplClazz;
+
+ try {
+ testAPIClazz = (Class<ProxyTest>) testClassLoader.loadClass(ProxyTest.class.getName());
+ testImplClazz = (Class<ProxyTest>) testClassLoader.loadClass(ProxyTestImpl.class.getName());
+ } catch (final Exception e) {
+ throw new RuntimeException(e);
+ }
+
+ final ProxyTest test = Guice.createInjector(new Module() {
+ public void configure(Binder binder) {
+
+ binder.bind(testAPIClazz).to(testImplClazz);
+
+ // Force proxy...
+ binder.bindInterceptor(any(), any(), new MethodInterceptor() {
+ public Object invoke(MethodInvocation chain)
+ throws Throwable {
+ return chain.proceed() + " WORLD";
+ }
+ });
+ }
+ }).getInstance(testAPIClazz);
+
+ // verify method interception still works
+ assertEquals("HELLO WORLD", test.sayHello());
+ }
+
+ public void testProxyClassUnloading() {
+
+ ProxyTest testProxy = Guice.createInjector(new Module() {
+ public void configure(Binder binder) {
+ binder.bind(ProxyTest.class).to(ProxyTestImpl.class);
+ binder.bindInterceptor(any(), any(), new MethodInterceptor() {
+ public Object invoke(MethodInvocation chain)
+ throws Throwable {
+ return chain.proceed() + " WORLD";
+ }
+ });
+ }
+ }).getInstance(ProxyTest.class);
+
+ // take a weak reference to the generated proxy class
+ Reference<Class<?>> clazzRef = new WeakReference<Class<?>>(testProxy.getClass());
+
+ assertNotNull(clazzRef.get());
+
+ // null the proxy
+ testProxy = null;
+
+ /*
+ * this should be enough to queue the weak reference
+ * unless something is holding onto it accidentally.
+ */
+ System.gc();
+ System.gc();
+ System.gc();
+
+ assertNull(clazzRef.get());
+ }
+}