Home | History | Annotate | Download | only in src
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      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 import java.io.BufferedReader;
     18 import java.io.File;
     19 import java.io.FileReader;
     20 import java.lang.ref.WeakReference;
     21 import java.lang.reflect.Constructor;
     22 import java.lang.reflect.Method;
     23 
     24 public class Main {
     25     static final String DEX_FILE = System.getenv("DEX_LOCATION") + "/141-class-unload-ex.jar";
     26     static final String LIBRARY_SEARCH_PATH = System.getProperty("java.library.path");
     27     static String nativeLibraryName;
     28 
     29     public static void main(String[] args) throws Exception {
     30         nativeLibraryName = args[0];
     31         Class<?> pathClassLoader = Class.forName("dalvik.system.PathClassLoader");
     32         if (pathClassLoader == null) {
     33             throw new AssertionError("Couldn't find path class loader class");
     34         }
     35         Constructor<?> constructor =
     36             pathClassLoader.getDeclaredConstructor(String.class, String.class, ClassLoader.class);
     37         try {
     38             testUnloadClass(constructor);
     39             testUnloadLoader(constructor);
     40             // Test that we don't unload if we have an instance.
     41             testNoUnloadInstance(constructor);
     42             // Test JNI_OnLoad and JNI_OnUnload.
     43             testLoadAndUnloadLibrary(constructor);
     44             // Test that stack traces keep the classes live.
     45             testStackTrace(constructor);
     46             // Stress test to make sure we dont leak memory.
     47             stressTest(constructor);
     48             // Test that the oat files are unloaded.
     49             testOatFilesUnloaded(getPid());
     50             // Test that objects keep class loader live for sticky GC.
     51             testStickyUnload(constructor);
     52         } catch (Exception e) {
     53             e.printStackTrace(System.out);
     54         }
     55     }
     56 
     57     private static void testOatFilesUnloaded(int pid) throws Exception {
     58         System.loadLibrary(nativeLibraryName);
     59         // Stop the JIT to ensure its threads and work queue are not keeping classes
     60         // artifically alive.
     61         stopJit();
     62         doUnloading();
     63         System.runFinalization();
     64         BufferedReader reader = new BufferedReader(new FileReader ("/proc/" + pid + "/maps"));
     65         String line;
     66         int count = 0;
     67         while ((line = reader.readLine()) != null) {
     68             if (line.contains("141-class-unload-ex.odex") ||
     69                 line.contains("141-class-unload-ex.vdex")) {
     70                 System.out.println(line);
     71                 ++count;
     72             }
     73         }
     74         System.out.println("Number of loaded unload-ex maps " + count);
     75         startJit();
     76     }
     77 
     78     private static void stressTest(Constructor<?> constructor) throws Exception {
     79         for (int i = 0; i <= 100; ++i) {
     80             setUpUnloadLoader(constructor, false);
     81             if (i % 10 == 0) {
     82                 Runtime.getRuntime().gc();
     83             }
     84         }
     85     }
     86 
     87     private static void doUnloading() {
     88       // Do multiple GCs to prevent rare flakiness if some other thread is keeping the
     89       // classloader live.
     90       for (int i = 0; i < 5; ++i) {
     91          Runtime.getRuntime().gc();
     92       }
     93     }
     94 
     95     private static void testUnloadClass(Constructor<?> constructor) throws Exception {
     96         WeakReference<Class> klass = setUpUnloadClassWeak(constructor);
     97         // No strong references to class loader, should get unloaded.
     98         doUnloading();
     99         WeakReference<Class> klass2 = setUpUnloadClassWeak(constructor);
    100         doUnloading();
    101         // If the weak reference is cleared, then it was unloaded.
    102         System.out.println(klass.get());
    103         System.out.println(klass2.get());
    104     }
    105 
    106     private static void testUnloadLoader(Constructor<?> constructor)
    107         throws Exception {
    108       WeakReference<ClassLoader> loader = setUpUnloadLoader(constructor, true);
    109       // No strong references to class loader, should get unloaded.
    110       doUnloading();
    111       // If the weak reference is cleared, then it was unloaded.
    112       System.out.println(loader.get());
    113     }
    114 
    115     private static void testStackTrace(Constructor<?> constructor) throws Exception {
    116         Class<?> klass = setUpUnloadClass(constructor);
    117         WeakReference<Class> weak_klass = new WeakReference(klass);
    118         Method stackTraceMethod = klass.getDeclaredMethod("generateStackTrace");
    119         Throwable throwable = (Throwable) stackTraceMethod.invoke(klass);
    120         stackTraceMethod = null;
    121         klass = null;
    122         doUnloading();
    123         boolean isNull = weak_klass.get() == null;
    124         System.out.println("class null " + isNull + " " + throwable.getMessage());
    125     }
    126 
    127     private static void testLoadAndUnloadLibrary(Constructor<?> constructor) throws Exception {
    128         WeakReference<ClassLoader> loader = setUpLoadLibrary(constructor);
    129         // No strong references to class loader, should get unloaded.
    130         doUnloading();
    131         // If the weak reference is cleared, then it was unloaded.
    132         System.out.println(loader.get());
    133     }
    134 
    135     private static Object testNoUnloadHelper(ClassLoader loader) throws Exception {
    136         Class<?> intHolder = loader.loadClass("IntHolder");
    137         return intHolder.newInstance();
    138     }
    139 
    140     static class Pair {
    141       public Pair(Object o, ClassLoader l) {
    142         object = o;
    143         classLoader = new WeakReference<ClassLoader>(l);
    144       }
    145 
    146       public Object object;
    147       public WeakReference<ClassLoader> classLoader;
    148     }
    149 
    150     private static Pair testNoUnloadInstanceHelper(Constructor<?> constructor) throws Exception {
    151         ClassLoader loader = (ClassLoader) constructor.newInstance(
    152             DEX_FILE, LIBRARY_SEARCH_PATH, ClassLoader.getSystemClassLoader());
    153         Object o = testNoUnloadHelper(loader);
    154         return new Pair(o, loader);
    155     }
    156 
    157     private static void testNoUnloadInstance(Constructor<?> constructor) throws Exception {
    158         Pair p = testNoUnloadInstanceHelper(constructor);
    159         doUnloading();
    160         // If the class loader was unloded too early due to races, just pass the test.
    161         boolean isNull = p.classLoader.get() == null;
    162         System.out.println("loader null " + isNull);
    163     }
    164 
    165     private static Class<?> setUpUnloadClass(Constructor<?> constructor) throws Exception {
    166         ClassLoader loader = (ClassLoader) constructor.newInstance(
    167             DEX_FILE, LIBRARY_SEARCH_PATH, ClassLoader.getSystemClassLoader());
    168         Class<?> intHolder = loader.loadClass("IntHolder");
    169         Method getValue = intHolder.getDeclaredMethod("getValue");
    170         Method setValue = intHolder.getDeclaredMethod("setValue", Integer.TYPE);
    171         // Make sure we don't accidentally preserve the value in the int holder, the class
    172         // initializer should be re-run.
    173         System.out.println((int) getValue.invoke(intHolder));
    174         setValue.invoke(intHolder, 2);
    175         System.out.println((int) getValue.invoke(intHolder));
    176         waitForCompilation(intHolder);
    177         return intHolder;
    178     }
    179 
    180     private static Object allocObjectInOtherClassLoader(Constructor<?> constructor)
    181             throws Exception {
    182       ClassLoader loader = (ClassLoader) constructor.newInstance(
    183               DEX_FILE, LIBRARY_SEARCH_PATH, ClassLoader.getSystemClassLoader());
    184       return loader.loadClass("IntHolder").newInstance();
    185     }
    186 
    187     // Regression test for public issue 227182.
    188     private static void testStickyUnload(Constructor<?> constructor) throws Exception {
    189         String s = "";
    190         for (int i = 0; i < 10; ++i) {
    191             s = "";
    192             // The object is the only thing preventing the class loader from being unloaded.
    193             Object o = allocObjectInOtherClassLoader(constructor);
    194             for (int j = 0; j < 1000; ++j) {
    195                 s += j + " ";
    196             }
    197             // Make sure the object still has a valid class (hasn't been incorrectly unloaded).
    198             s += o.getClass().getName();
    199             o = null;
    200         }
    201         System.out.println("Too small " + (s.length() < 1000));
    202     }
    203 
    204     private static WeakReference<Class> setUpUnloadClassWeak(Constructor<?> constructor)
    205             throws Exception {
    206         return new WeakReference<Class>(setUpUnloadClass(constructor));
    207     }
    208 
    209     private static WeakReference<ClassLoader> setUpUnloadLoader(Constructor<?> constructor,
    210                                                                 boolean waitForCompilation)
    211         throws Exception {
    212         ClassLoader loader = (ClassLoader) constructor.newInstance(
    213             DEX_FILE, LIBRARY_SEARCH_PATH, ClassLoader.getSystemClassLoader());
    214         Class<?> intHolder = loader.loadClass("IntHolder");
    215         Method setValue = intHolder.getDeclaredMethod("setValue", Integer.TYPE);
    216         setValue.invoke(intHolder, 2);
    217         if (waitForCompilation) {
    218             waitForCompilation(intHolder);
    219         }
    220         return new WeakReference(loader);
    221     }
    222 
    223     private static void waitForCompilation(Class<?> intHolder) throws Exception {
    224       // Load the native library so that we can call waitForCompilation.
    225       Method loadLibrary = intHolder.getDeclaredMethod("loadLibrary", String.class);
    226       loadLibrary.invoke(intHolder, nativeLibraryName);
    227       // Wait for JIT compilation to finish since the async threads may prevent unloading.
    228       Method waitForCompilation = intHolder.getDeclaredMethod("waitForCompilation");
    229       waitForCompilation.invoke(intHolder);
    230     }
    231 
    232     private static WeakReference<ClassLoader> setUpLoadLibrary(Constructor<?> constructor)
    233         throws Exception {
    234         ClassLoader loader = (ClassLoader) constructor.newInstance(
    235             DEX_FILE, LIBRARY_SEARCH_PATH, ClassLoader.getSystemClassLoader());
    236         Class<?> intHolder = loader.loadClass("IntHolder");
    237         Method loadLibrary = intHolder.getDeclaredMethod("loadLibrary", String.class);
    238         loadLibrary.invoke(intHolder, nativeLibraryName);
    239         waitForCompilation(intHolder);
    240         return new WeakReference(loader);
    241     }
    242 
    243     private static int getPid() throws Exception {
    244       return Integer.parseInt(new File("/proc/self").getCanonicalFile().getName());
    245     }
    246 
    247     public static native void stopJit();
    248     public static native void startJit();
    249 }
    250