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