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