• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2005 The Guava Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.common.base;
18 
19 import com.google.common.testing.GcFinalization;
20 
21 import junit.framework.TestCase;
22 
23 import java.io.Closeable;
24 import java.lang.ref.WeakReference;
25 import java.lang.reflect.Constructor;
26 import java.lang.reflect.Field;
27 import java.net.URL;
28 import java.net.URLClassLoader;
29 import java.security.Permission;
30 import java.security.Policy;
31 import java.security.ProtectionDomain;
32 import java.util.concurrent.Callable;
33 import java.util.concurrent.Semaphore;
34 import java.util.concurrent.TimeUnit;
35 import java.util.concurrent.atomic.AtomicReference;
36 
37 /**
38  * Tests that the {@code ClassLoader} of {@link FinalizableReferenceQueue} can be unloaded. These
39  * tests are separate from {@link FinalizableReferenceQueueTest} so that they can be excluded from
40  * coverage runs, as the coverage system interferes with them.
41  *
42  * @author Eamonn McManus
43  */
44 public class FinalizableReferenceQueueClassLoaderUnloadingTest extends TestCase {
45 
46   /*
47    * The following tests check that the use of FinalizableReferenceQueue does not prevent the
48    * ClassLoader that loaded that class from later being garbage-collected. If anything continues
49    * to reference the FinalizableReferenceQueue class then its ClassLoader cannot be
50    * garbage-collected, even if there are no more instances of FinalizableReferenceQueue itself.
51    * The code in FinalizableReferenceQueue goes to considerable trouble to ensure that there are
52    * no such references and the tests here check that that trouble has not been in vain.
53    *
54    * When we reference FinalizableReferenceQueue in this test, we are referencing a class that is
55    * loaded by this test and that will obviously remain loaded for as long as the test is running.
56    * So in order to check ClassLoader garbage collection we need to create a new ClassLoader and
57    * make it load its own version of FinalizableReferenceQueue. Then we need to interact with that
58    * parallel version through reflection in order to exercise the parallel
59    * FinalizableReferenceQueue, and then check that the parallel ClassLoader can be
60    * garbage-collected after that.
61    */
62 
63   public static class MyFinalizableWeakReference extends FinalizableWeakReference<Object> {
MyFinalizableWeakReference(Object x, FinalizableReferenceQueue queue)64     public MyFinalizableWeakReference(Object x, FinalizableReferenceQueue queue) {
65       super(x, queue);
66     }
67 
68     @Override
finalizeReferent()69     public void finalizeReferent() {
70     }
71   }
72 
73   private static class PermissivePolicy extends Policy {
74     @Override
implies(ProtectionDomain pd, Permission perm)75     public boolean implies(ProtectionDomain pd, Permission perm) {
76       return true;
77     }
78   }
79 
useFrqInSeparateLoader()80   private WeakReference<ClassLoader> useFrqInSeparateLoader() throws Exception {
81     final URLClassLoader myLoader = (URLClassLoader) getClass().getClassLoader();
82     final URL[] urls = myLoader.getURLs();
83     URLClassLoader sepLoader = new URLClassLoader(urls, myLoader.getParent());
84     // sepLoader is the loader that we will use to load the parallel FinalizableReferenceQueue (FRQ)
85     // and friends, and that we will eventually expect to see garbage-collected. The assumption
86     // is that the ClassLoader of this test is a URLClassLoader, and that it loads FRQ itself
87     // rather than delegating to a parent ClassLoader. If this assumption is violated the test will
88     // fail and will need to be rewritten.
89 
90     Class<?> frqC = FinalizableReferenceQueue.class;
91     Class<?> sepFrqC = sepLoader.loadClass(frqC.getName());
92     assertNotSame(frqC, sepFrqC);
93     // Check the assumptions above.
94 
95     // FRQ tries to load the Finalizer class (for the reference-collecting thread) in a few ways.
96     // If the class is accessible to the system ClassLoader (ClassLoader.getSystemClassLoader())
97     // then FRQ does not bother to load Finalizer.class through a separate ClassLoader. That happens
98     // in our test environment, which foils the purpose of this test, so we disable the logic for
99     // our test by setting a static field. We are changing the field in the parallel version of FRQ
100     // and each test creates its own one of those, so there is no test interference here.
101     Class<?> sepFrqSystemLoaderC =
102         sepLoader.loadClass(FinalizableReferenceQueue.SystemLoader.class.getName());
103     Field disabled = sepFrqSystemLoaderC.getDeclaredField("disabled");
104     disabled.setAccessible(true);
105     disabled.set(null, true);
106 
107     // Now make a parallel FRQ and an associated FinalizableWeakReference to an object, in order to
108     // exercise some classes from the parallel ClassLoader.
109     AtomicReference<Object> sepFrqA = new AtomicReference<Object>(sepFrqC.newInstance());
110     Class<?> sepFwrC = sepLoader.loadClass(MyFinalizableWeakReference.class.getName());
111     Constructor<?> sepFwrCons = sepFwrC.getConstructor(Object.class, sepFrqC);
112     // The object that we will wrap in FinalizableWeakReference is a Stopwatch.
113     Class<?> sepStopwatchC = sepLoader.loadClass(Stopwatch.class.getName());
114     assertSame(sepLoader, sepStopwatchC.getClassLoader());
115     AtomicReference<Object> sepStopwatchA =
116         new AtomicReference<Object>(sepStopwatchC.getMethod("createUnstarted").invoke(null));
117     AtomicReference<WeakReference<?>> sepStopwatchRef = new AtomicReference<WeakReference<?>>(
118         (WeakReference<?>) sepFwrCons.newInstance(sepStopwatchA.get(), sepFrqA.get()));
119     assertNotNull(sepStopwatchA.get());
120     // Clear all references to the Stopwatch and wait for it to be gc'd.
121     sepStopwatchA.set(null);
122     GcFinalization.awaitClear(sepStopwatchRef.get());
123     // Return a weak reference to the parallel ClassLoader. This is the reference that should
124     // eventually become clear if there are no other references to the ClassLoader.
125     return new WeakReference<ClassLoader>(sepLoader);
126   }
127 
doTestUnloadable()128   private void doTestUnloadable() throws Exception {
129     WeakReference<ClassLoader> loaderRef = useFrqInSeparateLoader();
130     GcFinalization.awaitClear(loaderRef);
131   }
132 
testUnloadableWithoutSecurityManager()133   public void testUnloadableWithoutSecurityManager() throws Exception {
134     // Test that the use of a FinalizableReferenceQueue does not subsequently prevent the
135     // loader of that class from being garbage-collected.
136     SecurityManager oldSecurityManager = System.getSecurityManager();
137     try {
138       System.setSecurityManager(null);
139       doTestUnloadable();
140     } finally {
141       System.setSecurityManager(oldSecurityManager);
142     }
143   }
144 
testUnloadableWithSecurityManager()145   public void testUnloadableWithSecurityManager() throws Exception {
146     // Test that the use of a FinalizableReferenceQueue does not subsequently prevent the
147     // loader of that class from being garbage-collected even if there is a SecurityManager.
148     // The SecurityManager environment makes such leaks more likely because when you create
149     // a URLClassLoader with a SecurityManager, the creating code's AccessControlContext is
150     // captured, and that references the creating code's ClassLoader.
151     Policy oldPolicy = Policy.getPolicy();
152     SecurityManager oldSecurityManager = System.getSecurityManager();
153     try {
154       Policy.setPolicy(new PermissivePolicy());
155       System.setSecurityManager(new SecurityManager());
156       doTestUnloadable();
157     } finally {
158       System.setSecurityManager(oldSecurityManager);
159       Policy.setPolicy(oldPolicy);
160     }
161   }
162 
163   public static class FrqUser implements Callable<WeakReference<Object>> {
164     public static FinalizableReferenceQueue frq = new FinalizableReferenceQueue();
165     public static final Semaphore finalized = new Semaphore(0);
166 
167     @Override
call()168     public WeakReference<Object> call() {
169       WeakReference<Object> wr = new FinalizableWeakReference<Object>(new Integer(23), frq) {
170         @Override
171         public void finalizeReferent() {
172           finalized.release();
173         }
174       };
175       return wr;
176     }
177   }
178 
testUnloadableInStaticFieldIfClosed()179   public void testUnloadableInStaticFieldIfClosed() throws Exception {
180     Policy oldPolicy = Policy.getPolicy();
181     SecurityManager oldSecurityManager = System.getSecurityManager();
182     try {
183       Policy.setPolicy(new PermissivePolicy());
184       System.setSecurityManager(new SecurityManager());
185       WeakReference<ClassLoader> loaderRef = doTestUnloadableInStaticFieldIfClosed();
186       GcFinalization.awaitClear(loaderRef);
187     } finally {
188       System.setSecurityManager(oldSecurityManager);
189       Policy.setPolicy(oldPolicy);
190     }
191   }
192 
193   // If you have a FinalizableReferenceQueue that is a static field of one of the classes of your
194   // app (like the FrqUser class above), then the app's ClassLoader will never be gc'd. The reason
195   // is that we attempt to run a thread in a separate ClassLoader that will detect when the FRQ
196   // is no longer referenced, meaning that the app's ClassLoader has been gc'd, and when that
197   // happens. But the thread's supposedly separate ClassLoader actually has a reference to the app's
198   // ClasLoader via its AccessControlContext. It does not seem to be possible to make a
199   // URLClassLoader without capturing this reference, and it probably would not be desirable for
200   // security reasons anyway. Therefore, the FRQ.close() method provides a way to stop the thread
201   // explicitly. This test checks that calling that method does allow an app's ClassLoader to be
202   // gc'd even if there is a still a FinalizableReferenceQueue in a static field. (Setting the field
203   // to null would also work, but only if there are no references to the FRQ anywhere else.)
doTestUnloadableInStaticFieldIfClosed()204   private WeakReference<ClassLoader> doTestUnloadableInStaticFieldIfClosed() throws Exception {
205     final URLClassLoader myLoader = (URLClassLoader) getClass().getClassLoader();
206     final URL[] urls = myLoader.getURLs();
207     URLClassLoader sepLoader = new URLClassLoader(urls, myLoader.getParent());
208 
209     Class<?> frqC = FinalizableReferenceQueue.class;
210     Class<?> sepFrqC = sepLoader.loadClass(frqC.getName());
211     assertNotSame(frqC, sepFrqC);
212 
213     Class<?> sepFrqSystemLoaderC =
214         sepLoader.loadClass(FinalizableReferenceQueue.SystemLoader.class.getName());
215     Field disabled = sepFrqSystemLoaderC.getDeclaredField("disabled");
216     disabled.setAccessible(true);
217     disabled.set(null, true);
218 
219     Class<?> frqUserC = FrqUser.class;
220     Class<?> sepFrqUserC = sepLoader.loadClass(frqUserC.getName());
221     assertNotSame(frqUserC, sepFrqUserC);
222     assertSame(sepLoader, sepFrqUserC.getClassLoader());
223 
224     Callable<?> sepFrqUser = (Callable<?>) sepFrqUserC.newInstance();
225     WeakReference<?> finalizableWeakReference = (WeakReference<?>) sepFrqUser.call();
226 
227     GcFinalization.awaitClear(finalizableWeakReference);
228 
229     Field sepFrqUserFinalizedF = sepFrqUserC.getField("finalized");
230     Semaphore finalizeCount = (Semaphore) sepFrqUserFinalizedF.get(null);
231     boolean finalized = finalizeCount.tryAcquire(5, TimeUnit.SECONDS);
232     assertTrue(finalized);
233 
234     Field sepFrqUserFrqF = sepFrqUserC.getField("frq");
235     Closeable frq = (Closeable) sepFrqUserFrqF.get(null);
236     frq.close();
237 
238     return new WeakReference<ClassLoader>(sepLoader);
239   }
240 }
241