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 main(String[] args)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(); 54 } 55 } 56 testOatFilesUnloaded(int pid)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.jar")) { 69 System.out.println(line); 70 ++count; 71 } 72 } 73 System.out.println("Number of loaded unload-ex maps " + count); 74 startJit(); 75 } 76 stressTest(Constructor<?> constructor)77 private static void stressTest(Constructor<?> constructor) throws Exception { 78 for (int i = 0; i <= 100; ++i) { 79 setUpUnloadLoader(constructor, false); 80 if (i % 10 == 0) { 81 Runtime.getRuntime().gc(); 82 } 83 } 84 } 85 doUnloading()86 private static void doUnloading() { 87 // Do multiple GCs to prevent rare flakiness if some other thread is keeping the 88 // classloader live. 89 for (int i = 0; i < 5; ++i) { 90 Runtime.getRuntime().gc(); 91 } 92 } 93 testUnloadClass(Constructor<?> constructor)94 private static void testUnloadClass(Constructor<?> constructor) throws Exception { 95 WeakReference<Class> klass = setUpUnloadClassWeak(constructor); 96 // No strong references to class loader, should get unloaded. 97 doUnloading(); 98 WeakReference<Class> klass2 = setUpUnloadClassWeak(constructor); 99 doUnloading(); 100 // If the weak reference is cleared, then it was unloaded. 101 System.out.println(klass.get()); 102 System.out.println(klass2.get()); 103 } 104 testUnloadLoader(Constructor<?> constructor)105 private static void testUnloadLoader(Constructor<?> constructor) 106 throws Exception { 107 WeakReference<ClassLoader> loader = setUpUnloadLoader(constructor, true); 108 // No strong references to class loader, should get unloaded. 109 doUnloading(); 110 // If the weak reference is cleared, then it was unloaded. 111 System.out.println(loader.get()); 112 } 113 testStackTrace(Constructor<?> constructor)114 private static void testStackTrace(Constructor<?> constructor) throws Exception { 115 Class<?> klass = setUpUnloadClass(constructor); 116 WeakReference<Class> weak_klass = new WeakReference(klass); 117 Method stackTraceMethod = klass.getDeclaredMethod("generateStackTrace"); 118 Throwable throwable = (Throwable) stackTraceMethod.invoke(klass); 119 stackTraceMethod = null; 120 klass = null; 121 doUnloading(); 122 boolean isNull = weak_klass.get() == null; 123 System.out.println("class null " + isNull + " " + throwable.getMessage()); 124 } 125 testLoadAndUnloadLibrary(Constructor<?> constructor)126 private static void testLoadAndUnloadLibrary(Constructor<?> constructor) throws Exception { 127 WeakReference<ClassLoader> loader = setUpLoadLibrary(constructor); 128 // No strong references to class loader, should get unloaded. 129 doUnloading(); 130 // If the weak reference is cleared, then it was unloaded. 131 System.out.println(loader.get()); 132 } 133 testNoUnloadHelper(ClassLoader loader)134 private static Object testNoUnloadHelper(ClassLoader loader) throws Exception { 135 Class<?> intHolder = loader.loadClass("IntHolder"); 136 return intHolder.newInstance(); 137 } 138 139 static class Pair { Pair(Object o, ClassLoader l)140 public Pair(Object o, ClassLoader l) { 141 object = o; 142 classLoader = new WeakReference<ClassLoader>(l); 143 } 144 145 public Object object; 146 public WeakReference<ClassLoader> classLoader; 147 } 148 testNoUnloadInstanceHelper(Constructor<?> constructor)149 private static Pair testNoUnloadInstanceHelper(Constructor<?> constructor) throws Exception { 150 ClassLoader loader = (ClassLoader) constructor.newInstance( 151 DEX_FILE, LIBRARY_SEARCH_PATH, ClassLoader.getSystemClassLoader()); 152 Object o = testNoUnloadHelper(loader); 153 return new Pair(o, loader); 154 } 155 testNoUnloadInstance(Constructor<?> constructor)156 private static void testNoUnloadInstance(Constructor<?> constructor) throws Exception { 157 Pair p = testNoUnloadInstanceHelper(constructor); 158 doUnloading(); 159 // If the class loader was unloded too early due to races, just pass the test. 160 boolean isNull = p.classLoader.get() == null; 161 System.out.println("loader null " + isNull); 162 } 163 setUpUnloadClass(Constructor<?> constructor)164 private static Class<?> setUpUnloadClass(Constructor<?> constructor) throws Exception { 165 ClassLoader loader = (ClassLoader) constructor.newInstance( 166 DEX_FILE, LIBRARY_SEARCH_PATH, ClassLoader.getSystemClassLoader()); 167 Class<?> intHolder = loader.loadClass("IntHolder"); 168 Method getValue = intHolder.getDeclaredMethod("getValue"); 169 Method setValue = intHolder.getDeclaredMethod("setValue", Integer.TYPE); 170 // Make sure we don't accidentally preserve the value in the int holder, the class 171 // initializer should be re-run. 172 System.out.println((int) getValue.invoke(intHolder)); 173 setValue.invoke(intHolder, 2); 174 System.out.println((int) getValue.invoke(intHolder)); 175 waitForCompilation(intHolder); 176 return intHolder; 177 } 178 allocObjectInOtherClassLoader(Constructor<?> constructor)179 private static Object allocObjectInOtherClassLoader(Constructor<?> constructor) 180 throws Exception { 181 ClassLoader loader = (ClassLoader) constructor.newInstance( 182 DEX_FILE, LIBRARY_SEARCH_PATH, ClassLoader.getSystemClassLoader()); 183 return loader.loadClass("IntHolder").newInstance(); 184 } 185 186 // Regression test for public issue 227182. testStickyUnload(Constructor<?> constructor)187 private static void testStickyUnload(Constructor<?> constructor) throws Exception { 188 String s = ""; 189 for (int i = 0; i < 10; ++i) { 190 s = ""; 191 // The object is the only thing preventing the class loader from being unloaded. 192 Object o = allocObjectInOtherClassLoader(constructor); 193 for (int j = 0; j < 1000; ++j) { 194 s += j + " "; 195 } 196 // Make sure the object still has a valid class (hasn't been incorrectly unloaded). 197 s += o.getClass().getName(); 198 o = null; 199 } 200 System.out.println("Too small " + (s.length() < 1000)); 201 } 202 setUpUnloadClassWeak(Constructor<?> constructor)203 private static WeakReference<Class> setUpUnloadClassWeak(Constructor<?> constructor) 204 throws Exception { 205 return new WeakReference<Class>(setUpUnloadClass(constructor)); 206 } 207 setUpUnloadLoader(Constructor<?> constructor, boolean waitForCompilation)208 private static WeakReference<ClassLoader> setUpUnloadLoader(Constructor<?> constructor, 209 boolean waitForCompilation) 210 throws Exception { 211 ClassLoader loader = (ClassLoader) constructor.newInstance( 212 DEX_FILE, LIBRARY_SEARCH_PATH, ClassLoader.getSystemClassLoader()); 213 Class<?> intHolder = loader.loadClass("IntHolder"); 214 Method setValue = intHolder.getDeclaredMethod("setValue", Integer.TYPE); 215 setValue.invoke(intHolder, 2); 216 if (waitForCompilation) { 217 waitForCompilation(intHolder); 218 } 219 return new WeakReference(loader); 220 } 221 waitForCompilation(Class<?> intHolder)222 private static void waitForCompilation(Class<?> intHolder) throws Exception { 223 // Load the native library so that we can call waitForCompilation. 224 Method loadLibrary = intHolder.getDeclaredMethod("loadLibrary", String.class); 225 loadLibrary.invoke(intHolder, nativeLibraryName); 226 // Wait for JIT compilation to finish since the async threads may prevent unloading. 227 Method waitForCompilation = intHolder.getDeclaredMethod("waitForCompilation"); 228 waitForCompilation.invoke(intHolder); 229 } 230 setUpLoadLibrary(Constructor<?> constructor)231 private static WeakReference<ClassLoader> setUpLoadLibrary(Constructor<?> constructor) 232 throws Exception { 233 ClassLoader loader = (ClassLoader) constructor.newInstance( 234 DEX_FILE, LIBRARY_SEARCH_PATH, ClassLoader.getSystemClassLoader()); 235 Class<?> intHolder = loader.loadClass("IntHolder"); 236 Method loadLibrary = intHolder.getDeclaredMethod("loadLibrary", String.class); 237 loadLibrary.invoke(intHolder, nativeLibraryName); 238 waitForCompilation(intHolder); 239 return new WeakReference(loader); 240 } 241 getPid()242 private static int getPid() throws Exception { 243 return Integer.parseInt(new File("/proc/self").getCanonicalFile().getName()); 244 } 245 stopJit()246 public static native void stopJit(); startJit()247 public static native void startJit(); 248 } 249