• 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 static com.google.common.base.StandardSystemProperty.JAVA_CLASS_PATH;
20 import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION;
21 import static com.google.common.base.StandardSystemProperty.PATH_SEPARATOR;
22 
23 import com.google.common.collect.ImmutableList;
24 import com.google.common.testing.GcFinalization;
25 import java.io.Closeable;
26 import java.io.File;
27 import java.lang.ref.WeakReference;
28 import java.lang.reflect.Constructor;
29 import java.lang.reflect.Field;
30 import java.net.MalformedURLException;
31 import java.net.URL;
32 import java.net.URLClassLoader;
33 import java.security.Permission;
34 import java.security.Policy;
35 import java.security.ProtectionDomain;
36 import java.util.concurrent.Callable;
37 import java.util.concurrent.Semaphore;
38 import java.util.concurrent.TimeUnit;
39 import java.util.concurrent.atomic.AtomicReference;
40 import junit.framework.TestCase;
41 
42 /**
43  * Tests that the {@code ClassLoader} of {@link FinalizableReferenceQueue} can be unloaded. These
44  * tests are separate from {@link FinalizableReferenceQueueTest} so that they can be excluded from
45  * coverage runs, as the coverage system interferes with them.
46  *
47  * @author Eamonn McManus
48  */
49 
50 
51 public class FinalizableReferenceQueueClassLoaderUnloadingTest extends TestCase {
52 
53   /*
54    * The following tests check that the use of FinalizableReferenceQueue does not prevent the
55    * ClassLoader that loaded that class from later being garbage-collected. If anything continues
56    * to reference the FinalizableReferenceQueue class then its ClassLoader cannot be
57    * garbage-collected, even if there are no more instances of FinalizableReferenceQueue itself.
58    * The code in FinalizableReferenceQueue goes to considerable trouble to ensure that there are
59    * no such references and the tests here check that that trouble has not been in vain.
60    *
61    * When we reference FinalizableReferenceQueue in this test, we are referencing a class that is
62    * loaded by this test and that will obviously remain loaded for as long as the test is running.
63    * So in order to check ClassLoader garbage collection we need to create a new ClassLoader and
64    * make it load its own version of FinalizableReferenceQueue. Then we need to interact with that
65    * parallel version through reflection in order to exercise the parallel
66    * FinalizableReferenceQueue, and then check that the parallel ClassLoader can be
67    * garbage-collected after that.
68    */
69 
70   public static class MyFinalizableWeakReference extends FinalizableWeakReference<Object> {
MyFinalizableWeakReference(Object x, FinalizableReferenceQueue queue)71     public MyFinalizableWeakReference(Object x, FinalizableReferenceQueue queue) {
72       super(x, queue);
73     }
74 
75     @Override
finalizeReferent()76     public void finalizeReferent() {}
77   }
78 
79   private static class PermissivePolicy extends Policy {
80     @Override
implies(ProtectionDomain pd, Permission perm)81     public boolean implies(ProtectionDomain pd, Permission perm) {
82       return true;
83     }
84   }
85 
useFrqInSeparateLoader()86   private WeakReference<ClassLoader> useFrqInSeparateLoader() throws Exception {
87     final ClassLoader myLoader = getClass().getClassLoader();
88     URLClassLoader sepLoader = new URLClassLoader(getClassPathUrls(), myLoader.getParent());
89     // sepLoader is the loader that we will use to load the parallel FinalizableReferenceQueue (FRQ)
90     // and friends, and that we will eventually expect to see garbage-collected. The assumption
91     // is that the ClassLoader of this test is a URLClassLoader, and that it loads FRQ itself
92     // rather than delegating to a parent ClassLoader. If this assumption is violated the test will
93     // fail and will need to be rewritten.
94 
95     Class<?> frqC = FinalizableReferenceQueue.class;
96     Class<?> sepFrqC = sepLoader.loadClass(frqC.getName());
97     assertNotSame(frqC, sepFrqC);
98     // Check the assumptions above.
99 
100     // FRQ tries to load the Finalizer class (for the reference-collecting thread) in a few ways.
101     // If the class is accessible to the system ClassLoader (ClassLoader.getSystemClassLoader())
102     // then FRQ does not bother to load Finalizer.class through a separate ClassLoader. That happens
103     // in our test environment, which foils the purpose of this test, so we disable the logic for
104     // our test by setting a static field. We are changing the field in the parallel version of FRQ
105     // and each test creates its own one of those, so there is no test interference here.
106     Class<?> sepFrqSystemLoaderC =
107         sepLoader.loadClass(FinalizableReferenceQueue.SystemLoader.class.getName());
108     Field disabled = sepFrqSystemLoaderC.getDeclaredField("disabled");
109     disabled.setAccessible(true);
110     disabled.set(null, true);
111 
112     // Now make a parallel FRQ and an associated FinalizableWeakReference to an object, in order to
113     // exercise some classes from the parallel ClassLoader.
114     AtomicReference<Object> sepFrqA =
115         new AtomicReference<Object>(sepFrqC.getDeclaredConstructor().newInstance());
116     Class<?> sepFwrC = sepLoader.loadClass(MyFinalizableWeakReference.class.getName());
117     Constructor<?> sepFwrCons = sepFwrC.getConstructor(Object.class, sepFrqC);
118     // The object that we will wrap in FinalizableWeakReference is a Stopwatch.
119     Class<?> sepStopwatchC = sepLoader.loadClass(Stopwatch.class.getName());
120     assertSame(sepLoader, sepStopwatchC.getClassLoader());
121     AtomicReference<Object> sepStopwatchA =
122         new AtomicReference<Object>(sepStopwatchC.getMethod("createUnstarted").invoke(null));
123     AtomicReference<WeakReference<?>> sepStopwatchRef =
124         new AtomicReference<WeakReference<?>>(
125             (WeakReference<?>) sepFwrCons.newInstance(sepStopwatchA.get(), sepFrqA.get()));
126     assertNotNull(sepStopwatchA.get());
127     // Clear all references to the Stopwatch and wait for it to be gc'd.
128     sepStopwatchA.set(null);
129     GcFinalization.awaitClear(sepStopwatchRef.get());
130     // Return a weak reference to the parallel ClassLoader. This is the reference that should
131     // eventually become clear if there are no other references to the ClassLoader.
132     return new WeakReference<ClassLoader>(sepLoader);
133   }
134 
doTestUnloadable()135   private void doTestUnloadable() throws Exception {
136     WeakReference<ClassLoader> loaderRef = useFrqInSeparateLoader();
137     GcFinalization.awaitClear(loaderRef);
138   }
139 
140   /**
141    * Tests that the use of a {@link FinalizableReferenceQueue} does not subsequently prevent the
142    * loader of that class from being garbage-collected.
143    */
testUnloadableWithoutSecurityManager()144   public void testUnloadableWithoutSecurityManager() throws Exception {
145     if (isJdk9OrHigher()) {
146       return;
147     }
148     SecurityManager oldSecurityManager = System.getSecurityManager();
149     try {
150       System.setSecurityManager(null);
151       doTestUnloadable();
152     } finally {
153       System.setSecurityManager(oldSecurityManager);
154     }
155   }
156 
157   /**
158    * Tests that the use of a {@link FinalizableReferenceQueue} does not subsequently prevent the
159    * loader of that class from being garbage-collected even if there is a {@link SecurityManager}.
160    * The {@link SecurityManager} environment makes such leaks more likely because when you create a
161    * {@link URLClassLoader} with a {@link SecurityManager}, the creating code's {@link
162    * java.security.AccessControlContext} is captured, and that references the creating code's {@link
163    * ClassLoader}.
164    */
testUnloadableWithSecurityManager()165   public void testUnloadableWithSecurityManager() throws Exception {
166     if (isJdk9OrHigher()) {
167       return;
168     }
169     Policy oldPolicy = Policy.getPolicy();
170     SecurityManager oldSecurityManager = System.getSecurityManager();
171     try {
172       Policy.setPolicy(new PermissivePolicy());
173       System.setSecurityManager(new SecurityManager());
174       doTestUnloadable();
175     } finally {
176       System.setSecurityManager(oldSecurityManager);
177       Policy.setPolicy(oldPolicy);
178     }
179   }
180 
181   public static class FrqUser implements Callable<WeakReference<Object>> {
182     public static FinalizableReferenceQueue frq = new FinalizableReferenceQueue();
183     public static final Semaphore finalized = new Semaphore(0);
184 
185     @Override
call()186     public WeakReference<Object> call() {
187       WeakReference<Object> wr =
188           new FinalizableWeakReference<Object>(new Integer(23), frq) {
189             @Override
190             public void finalizeReferent() {
191               finalized.release();
192             }
193           };
194       return wr;
195     }
196   }
197 
testUnloadableInStaticFieldIfClosed()198   public void testUnloadableInStaticFieldIfClosed() throws Exception {
199     if (isJdk9OrHigher()) {
200       return;
201     }
202     Policy oldPolicy = Policy.getPolicy();
203     SecurityManager oldSecurityManager = System.getSecurityManager();
204     try {
205       Policy.setPolicy(new PermissivePolicy());
206       System.setSecurityManager(new SecurityManager());
207       WeakReference<ClassLoader> loaderRef = doTestUnloadableInStaticFieldIfClosed();
208       GcFinalization.awaitClear(loaderRef);
209     } finally {
210       System.setSecurityManager(oldSecurityManager);
211       Policy.setPolicy(oldPolicy);
212     }
213   }
214 
215   // If you have a FinalizableReferenceQueue that is a static field of one of the classes of your
216   // app (like the FrqUser class above), then the app's ClassLoader will never be gc'd. The reason
217   // is that we attempt to run a thread in a separate ClassLoader that will detect when the FRQ
218   // is no longer referenced, meaning that the app's ClassLoader has been gc'd, and when that
219   // happens. But the thread's supposedly separate ClassLoader actually has a reference to the app's
220   // ClasLoader via its AccessControlContext. It does not seem to be possible to make a
221   // URLClassLoader without capturing this reference, and it probably would not be desirable for
222   // security reasons anyway. Therefore, the FRQ.close() method provides a way to stop the thread
223   // explicitly. This test checks that calling that method does allow an app's ClassLoader to be
224   // gc'd even if there is a still a FinalizableReferenceQueue in a static field. (Setting the field
225   // to null would also work, but only if there are no references to the FRQ anywhere else.)
doTestUnloadableInStaticFieldIfClosed()226   private WeakReference<ClassLoader> doTestUnloadableInStaticFieldIfClosed() throws Exception {
227     final ClassLoader myLoader = getClass().getClassLoader();
228     URLClassLoader sepLoader = new URLClassLoader(getClassPathUrls(), myLoader.getParent());
229 
230     Class<?> frqC = FinalizableReferenceQueue.class;
231     Class<?> sepFrqC = sepLoader.loadClass(frqC.getName());
232     assertNotSame(frqC, sepFrqC);
233 
234     Class<?> sepFrqSystemLoaderC =
235         sepLoader.loadClass(FinalizableReferenceQueue.SystemLoader.class.getName());
236     Field disabled = sepFrqSystemLoaderC.getDeclaredField("disabled");
237     disabled.setAccessible(true);
238     disabled.set(null, true);
239 
240     Class<?> frqUserC = FrqUser.class;
241     Class<?> sepFrqUserC = sepLoader.loadClass(frqUserC.getName());
242     assertNotSame(frqUserC, sepFrqUserC);
243     assertSame(sepLoader, sepFrqUserC.getClassLoader());
244 
245     Callable<?> sepFrqUser = (Callable<?>) sepFrqUserC.getDeclaredConstructor().newInstance();
246     WeakReference<?> finalizableWeakReference = (WeakReference<?>) sepFrqUser.call();
247 
248     GcFinalization.awaitClear(finalizableWeakReference);
249 
250     Field sepFrqUserFinalizedF = sepFrqUserC.getField("finalized");
251     Semaphore finalizeCount = (Semaphore) sepFrqUserFinalizedF.get(null);
252     boolean finalized = finalizeCount.tryAcquire(5, TimeUnit.SECONDS);
253     assertTrue(finalized);
254 
255     Field sepFrqUserFrqF = sepFrqUserC.getField("frq");
256     Closeable frq = (Closeable) sepFrqUserFrqF.get(null);
257     frq.close();
258 
259     return new WeakReference<ClassLoader>(sepLoader);
260   }
261 
getClassPathUrls()262   private URL[] getClassPathUrls() {
263     ClassLoader classLoader = getClass().getClassLoader();
264     return classLoader instanceof URLClassLoader
265         ? ((URLClassLoader) classLoader).getURLs()
266         : parseJavaClassPath().toArray(new URL[0]);
267   }
268 
269   /**
270    * Returns the URLs in the class path specified by the {@code java.class.path} {@linkplain
271    * System#getProperty system property}.
272    */
273   // TODO(b/65488446): Make this a public API.
parseJavaClassPath()274   private static ImmutableList<URL> parseJavaClassPath() {
275     ImmutableList.Builder<URL> urls = ImmutableList.builder();
276     for (String entry : Splitter.on(PATH_SEPARATOR.value()).split(JAVA_CLASS_PATH.value())) {
277       try {
278         try {
279           urls.add(new File(entry).toURI().toURL());
280         } catch (SecurityException e) { // File.toURI checks to see if the file is a directory
281           urls.add(new URL("file", null, new File(entry).getAbsolutePath()));
282         }
283       } catch (MalformedURLException e) {
284         throw new AssertionError("malformed class path entry: " + entry, e);
285       }
286     }
287     return urls.build();
288   }
289 
290   /**
291    * These tests fail in JDK 9 and JDK 10 for an unknown reason. It might be the test; it might be
292    * the underlying functionality. Fixing this is not a high priority; if you need it to be fixed,
293    * please comment on <a href="https://github.com/google/guava/issues/3086">issue 3086</a>.
294    */
isJdk9OrHigher()295   private static boolean isJdk9OrHigher() {
296     return JAVA_SPECIFICATION_VERSION.value().startsWith("9")
297         || JAVA_SPECIFICATION_VERSION.value().startsWith("10");
298   }
299 }
300