Home | History | Annotate | Download | only in base
      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