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 a Method keeping the class live.
     41             testNoUnloadInvoke(constructor);
     42             // Test that we don't unload if we have an instance.
     43             testNoUnloadInstance(constructor);
     44             // Test JNI_OnLoad and JNI_OnUnload.
     45             testLoadAndUnloadLibrary(constructor);
     46             // Test that stack traces keep the classes live.
     47             testStackTrace(constructor);
     48             // Stress test to make sure we dont leak memory.
     49             stressTest(constructor);
     50             // Test that the oat files are unloaded.
     51             testOatFilesUnloaded(getPid());
     52         } catch (Exception e) {
     53             e.printStackTrace();
     54         }
     55     }
     56 
     57     private static void testOatFilesUnloaded(int pid) throws Exception {
     58         BufferedReader reader = new BufferedReader(new FileReader ("/proc/" + pid + "/maps"));
     59         String line;
     60         int count = 0;
     61         Runtime.getRuntime().gc();
     62         System.runFinalization();
     63         while ((line = reader.readLine()) != null) {
     64             if (line.contains("@141-class-unload-ex.jar")) {
     65                 System.out.println(line);
     66                 ++count;
     67             }
     68         }
     69         System.out.println("Number of loaded unload-ex maps " + count);
     70     }
     71 
     72     private static void stressTest(Constructor constructor) throws Exception {
     73         for (int i = 0; i <= 100; ++i) {
     74             setUpUnloadLoader(constructor, false);
     75             if (i % 10 == 0) {
     76                 Runtime.getRuntime().gc();
     77             }
     78         }
     79     }
     80 
     81     private static void testUnloadClass(Constructor constructor) throws Exception {
     82         WeakReference<Class> klass = setUpUnloadClass(constructor);
     83         // No strong references to class loader, should get unloaded.
     84         Runtime.getRuntime().gc();
     85         WeakReference<Class> klass2 = setUpUnloadClass(constructor);
     86         Runtime.getRuntime().gc();
     87         // If the weak reference is cleared, then it was unloaded.
     88         System.out.println(klass.get());
     89         System.out.println(klass2.get());
     90     }
     91 
     92     private static void testUnloadLoader(Constructor constructor)
     93         throws Exception {
     94       WeakReference<ClassLoader> loader = setUpUnloadLoader(constructor, true);
     95       // No strong references to class loader, should get unloaded.
     96       Runtime.getRuntime().gc();
     97       // If the weak reference is cleared, then it was unloaded.
     98       System.out.println(loader.get());
     99     }
    100 
    101     private static void testStackTrace(Constructor constructor) throws Exception {
    102         WeakReference<Class> klass = setUpUnloadClass(constructor);
    103         Method stackTraceMethod = klass.get().getDeclaredMethod("generateStackTrace");
    104         Throwable throwable = (Throwable) stackTraceMethod.invoke(klass.get());
    105         stackTraceMethod = null;
    106         Runtime.getRuntime().gc();
    107         boolean isNull = klass.get() == null;
    108         System.out.println("class null " + isNull + " " + throwable.getMessage());
    109     }
    110 
    111     private static void testLoadAndUnloadLibrary(Constructor constructor) throws Exception {
    112         WeakReference<ClassLoader> loader = setUpLoadLibrary(constructor);
    113         // No strong references to class loader, should get unloaded.
    114         Runtime.getRuntime().gc();
    115         // If the weak reference is cleared, then it was unloaded.
    116         System.out.println(loader.get());
    117     }
    118 
    119     private static void testNoUnloadInvoke(Constructor constructor) throws Exception {
    120         WeakReference<ClassLoader> loader =
    121             new WeakReference((ClassLoader) constructor.newInstance(
    122                 DEX_FILE, LIBRARY_SEARCH_PATH, ClassLoader.getSystemClassLoader()));
    123         WeakReference<Class> intHolder = new WeakReference(loader.get().loadClass("IntHolder"));
    124         intHolder.get().getDeclaredMethod("runGC").invoke(intHolder.get());
    125         boolean isNull = loader.get() == null;
    126         System.out.println("loader null " + isNull);
    127     }
    128 
    129     private static void testNoUnloadInstance(Constructor constructor) throws Exception {
    130         WeakReference<ClassLoader> loader =
    131             new WeakReference((ClassLoader) constructor.newInstance(
    132                 DEX_FILE, LIBRARY_SEARCH_PATH, ClassLoader.getSystemClassLoader()));
    133         WeakReference<Class> intHolder = new WeakReference(loader.get().loadClass("IntHolder"));
    134         Object o = intHolder.get().newInstance();
    135         Runtime.getRuntime().gc();
    136         boolean isNull = loader.get() == null;
    137         System.out.println("loader null " + isNull);
    138     }
    139 
    140     private static WeakReference<Class> setUpUnloadClass(Constructor constructor) throws Exception {
    141         ClassLoader loader = (ClassLoader) constructor.newInstance(
    142             DEX_FILE, LIBRARY_SEARCH_PATH, ClassLoader.getSystemClassLoader());
    143         Class intHolder = loader.loadClass("IntHolder");
    144         Method getValue = intHolder.getDeclaredMethod("getValue");
    145         Method setValue = intHolder.getDeclaredMethod("setValue", Integer.TYPE);
    146         // Make sure we don't accidentally preserve the value in the int holder, the class
    147         // initializer should be re-run.
    148         System.out.println((int) getValue.invoke(intHolder));
    149         setValue.invoke(intHolder, 2);
    150         System.out.println((int) getValue.invoke(intHolder));
    151         waitForCompilation(intHolder);
    152         return new WeakReference(intHolder);
    153     }
    154 
    155     private static WeakReference<ClassLoader> setUpUnloadLoader(Constructor constructor,
    156                                                                 boolean waitForCompilation)
    157         throws Exception {
    158         ClassLoader loader = (ClassLoader) constructor.newInstance(
    159             DEX_FILE, LIBRARY_SEARCH_PATH, ClassLoader.getSystemClassLoader());
    160         Class intHolder = loader.loadClass("IntHolder");
    161         Method setValue = intHolder.getDeclaredMethod("setValue", Integer.TYPE);
    162         setValue.invoke(intHolder, 2);
    163         if (waitForCompilation) {
    164             waitForCompilation(intHolder);
    165         }
    166         return new WeakReference(loader);
    167     }
    168 
    169     private static void waitForCompilation(Class intHolder) throws Exception {
    170       // Load the native library so that we can call waitForCompilation.
    171       Method loadLibrary = intHolder.getDeclaredMethod("loadLibrary", String.class);
    172       loadLibrary.invoke(intHolder, nativeLibraryName);
    173       // Wait for JIT compilation to finish since the async threads may prevent unloading.
    174       Method waitForCompilation = intHolder.getDeclaredMethod("waitForCompilation");
    175       waitForCompilation.invoke(intHolder);
    176     }
    177 
    178     private static WeakReference<ClassLoader> setUpLoadLibrary(Constructor constructor)
    179         throws Exception {
    180         ClassLoader loader = (ClassLoader) constructor.newInstance(
    181             DEX_FILE, LIBRARY_SEARCH_PATH, ClassLoader.getSystemClassLoader());
    182         Class intHolder = loader.loadClass("IntHolder");
    183         Method loadLibrary = intHolder.getDeclaredMethod("loadLibrary", String.class);
    184         loadLibrary.invoke(intHolder, nativeLibraryName);
    185         waitForCompilation(intHolder);
    186         return new WeakReference(loader);
    187     }
    188 
    189     private static int getPid() throws Exception {
    190       return Integer.parseInt(new File("/proc/self").getCanonicalFile().getName());
    191     }
    192 }
    193