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 import java.util.Arrays; 24 import java.util.function.Consumer; 25 26 public class Main { 27 static final String DEX_FILE = System.getenv("DEX_LOCATION") + "/141-class-unload-ex.jar"; 28 static final String LIBRARY_SEARCH_PATH = System.getProperty("java.library.path"); 29 static String nativeLibraryName; 30 main(String[] args)31 public static void main(String[] args) throws Exception { 32 nativeLibraryName = args[0]; 33 Class<?> pathClassLoader = Class.forName("dalvik.system.PathClassLoader"); 34 if (pathClassLoader == null) { 35 throw new AssertionError("Couldn't find path class loader class"); 36 } 37 Constructor<?> constructor = 38 pathClassLoader.getDeclaredConstructor(String.class, String.class, ClassLoader.class); 39 try { 40 testUnloadClass(constructor); 41 testUnloadLoader(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 // Test that objects keep class loader live for sticky GC. 53 testStickyUnload(constructor); 54 // Test that copied methods recorded in a stack trace prevents unloading. 55 testCopiedMethodInStackTrace(constructor); 56 // Test that code preventing unloading holder classes of copied methods recorded in 57 // a stack trace does not crash when processing a copied method in the boot class path. 58 testCopiedBcpMethodInStackTrace(); 59 // Test that code preventing unloading holder classes of copied methods recorded in 60 // a stack trace does not crash when processing a copied method in an app image. 61 testCopiedAppImageMethodInStackTrace(); 62 // Test that the runtime uses the right allocator when creating conflict methods. 63 testConflictMethod(constructor); 64 } catch (Exception e) { 65 e.printStackTrace(System.out); 66 } 67 } 68 testOatFilesUnloaded(int pid)69 private static void testOatFilesUnloaded(int pid) throws Exception { 70 System.loadLibrary(nativeLibraryName); 71 // Stop the JIT to ensure its threads and work queue are not keeping classes 72 // artifically alive. 73 stopJit(); 74 doUnloading(); 75 System.runFinalization(); 76 BufferedReader reader = new BufferedReader(new FileReader ("/proc/" + pid + "/maps")); 77 String line; 78 int count = 0; 79 while ((line = reader.readLine()) != null) { 80 if (line.contains("141-class-unload-ex.odex") || 81 line.contains("141-class-unload-ex.vdex")) { 82 System.out.println(line); 83 ++count; 84 } 85 } 86 System.out.println("Number of loaded unload-ex maps " + count); 87 startJit(); 88 } 89 stressTest(Constructor<?> constructor)90 private static void stressTest(Constructor<?> constructor) throws Exception { 91 for (int i = 0; i <= 100; ++i) { 92 setUpUnloadLoader(constructor, false); 93 if (i % 10 == 0) { 94 Runtime.getRuntime().gc(); 95 } 96 } 97 } 98 doUnloading()99 private static void doUnloading() { 100 // Do multiple GCs to prevent rare flakiness if some other thread is keeping the 101 // classloader live. 102 for (int i = 0; i < 5; ++i) { 103 Runtime.getRuntime().gc(); 104 } 105 } 106 testUnloadClass(Constructor<?> constructor)107 private static void testUnloadClass(Constructor<?> constructor) throws Exception { 108 WeakReference<Class> klass = setUpUnloadClassWeak(constructor); 109 // No strong references to class loader, should get unloaded. 110 doUnloading(); 111 WeakReference<Class> klass2 = setUpUnloadClassWeak(constructor); 112 doUnloading(); 113 // If the weak reference is cleared, then it was unloaded. 114 System.out.println(klass.get()); 115 System.out.println(klass2.get()); 116 } 117 testUnloadLoader(Constructor<?> constructor)118 private static void testUnloadLoader(Constructor<?> constructor) throws Exception { 119 WeakReference<ClassLoader> loader = setUpUnloadLoader(constructor, true); 120 // No strong references to class loader, should get unloaded. 121 doUnloading(); 122 // If the weak reference is cleared, then it was unloaded. 123 System.out.println(loader.get()); 124 } 125 testStackTrace(Constructor<?> constructor)126 private static void testStackTrace(Constructor<?> constructor) throws Exception { 127 Class<?> klass = setUpUnloadClass(constructor); 128 WeakReference<Class> weak_klass = new WeakReference(klass); 129 Method stackTraceMethod = klass.getDeclaredMethod("generateStackTrace"); 130 Throwable throwable = (Throwable) stackTraceMethod.invoke(klass); 131 stackTraceMethod = null; 132 klass = null; 133 doUnloading(); 134 boolean isNull = weak_klass.get() == null; 135 System.out.println("class null " + isNull + " " + throwable.getMessage()); 136 } 137 testLoadAndUnloadLibrary(Constructor<?> constructor)138 private static void testLoadAndUnloadLibrary(Constructor<?> constructor) throws Exception { 139 WeakReference<ClassLoader> loader = setUpLoadLibrary(constructor); 140 // No strong references to class loader, should get unloaded. 141 doUnloading(); 142 // If the weak reference is cleared, then it was unloaded. 143 System.out.println(loader.get()); 144 } 145 testNoUnloadHelper(ClassLoader loader)146 private static Object testNoUnloadHelper(ClassLoader loader) throws Exception { 147 Class<?> intHolder = loader.loadClass("IntHolder"); 148 return intHolder.newInstance(); 149 } 150 151 static class Pair { Pair(Object o, ClassLoader l)152 public Pair(Object o, ClassLoader l) { 153 object = o; 154 classLoader = new WeakReference<ClassLoader>(l); 155 } 156 157 public Object object; 158 public WeakReference<ClassLoader> classLoader; 159 } 160 161 // Make the method not inline-able to prevent the compiler optimizing away the allocation. $noinline$testNoUnloadInstanceHelper(Constructor<?> constructor)162 private static Pair $noinline$testNoUnloadInstanceHelper(Constructor<?> constructor) 163 throws Exception { 164 ClassLoader loader = (ClassLoader) constructor.newInstance( 165 DEX_FILE, LIBRARY_SEARCH_PATH, ClassLoader.getSystemClassLoader()); 166 Object o = testNoUnloadHelper(loader); 167 return new Pair(o, loader); 168 } 169 testNoUnloadInstance(Constructor<?> constructor)170 private static void testNoUnloadInstance(Constructor<?> constructor) throws Exception { 171 Pair p = $noinline$testNoUnloadInstanceHelper(constructor); 172 doUnloading(); 173 boolean isNull = p.classLoader.get() == null; 174 System.out.println("loader null " + isNull); 175 } 176 setUpUnloadClass(Constructor<?> constructor)177 private static Class<?> setUpUnloadClass(Constructor<?> constructor) throws Exception { 178 ClassLoader loader = (ClassLoader) constructor.newInstance( 179 DEX_FILE, LIBRARY_SEARCH_PATH, ClassLoader.getSystemClassLoader()); 180 Class<?> intHolder = loader.loadClass("IntHolder"); 181 Method getValue = intHolder.getDeclaredMethod("getValue"); 182 Method setValue = intHolder.getDeclaredMethod("setValue", Integer.TYPE); 183 // Make sure we don't accidentally preserve the value in the int holder, the class 184 // initializer should be re-run. 185 System.out.println((int) getValue.invoke(intHolder)); 186 setValue.invoke(intHolder, 2); 187 System.out.println((int) getValue.invoke(intHolder)); 188 waitForCompilation(intHolder); 189 return intHolder; 190 } 191 allocObjectInOtherClassLoader(Constructor<?> constructor)192 private static Object allocObjectInOtherClassLoader(Constructor<?> constructor) 193 throws Exception { 194 ClassLoader loader = (ClassLoader) constructor.newInstance( 195 DEX_FILE, LIBRARY_SEARCH_PATH, ClassLoader.getSystemClassLoader()); 196 return loader.loadClass("IntHolder").newInstance(); 197 } 198 199 // Regression test for public issue 227182. testStickyUnload(Constructor<?> constructor)200 private static void testStickyUnload(Constructor<?> constructor) throws Exception { 201 String s = ""; 202 for (int i = 0; i < 10; ++i) { 203 s = ""; 204 // The object is the only thing preventing the class loader from being unloaded. 205 Object o = allocObjectInOtherClassLoader(constructor); 206 for (int j = 0; j < 1000; ++j) { 207 s += j + " "; 208 } 209 // Make sure the object still has a valid class (hasn't been incorrectly unloaded). 210 s += o.getClass().getName(); 211 o = null; 212 } 213 System.out.println("Too small " + (s.length() < 1000)); 214 } 215 assertStackTraceContains(Throwable t, String className, String methodName)216 private static void assertStackTraceContains(Throwable t, String className, String methodName) { 217 boolean found = false; 218 for (StackTraceElement e : t.getStackTrace()) { 219 if (className.equals(e.getClassName()) && methodName.equals(e.getMethodName())) { 220 found = true; 221 break; 222 } 223 } 224 if (!found) { 225 throw new Error("Did not find " + className + "." + methodName); 226 } 227 } 228 $noinline$callAllMethods(ConflictIface iface)229 private static void $noinline$callAllMethods(ConflictIface iface) { 230 // Call all methods in the interface to make sure we hit conflicts in the IMT. 231 iface.method1(); 232 iface.method2(); 233 iface.method3(); 234 iface.method4(); 235 iface.method5(); 236 iface.method6(); 237 iface.method7(); 238 iface.method8(); 239 iface.method9(); 240 iface.method10(); 241 iface.method11(); 242 iface.method12(); 243 iface.method13(); 244 iface.method14(); 245 iface.method15(); 246 iface.method16(); 247 iface.method17(); 248 iface.method18(); 249 iface.method19(); 250 iface.method20(); 251 iface.method21(); 252 iface.method22(); 253 iface.method23(); 254 iface.method24(); 255 iface.method25(); 256 iface.method26(); 257 iface.method27(); 258 iface.method28(); 259 iface.method29(); 260 iface.method30(); 261 iface.method31(); 262 iface.method32(); 263 iface.method33(); 264 iface.method34(); 265 iface.method35(); 266 iface.method36(); 267 iface.method37(); 268 iface.method38(); 269 iface.method39(); 270 iface.method40(); 271 iface.method41(); 272 iface.method42(); 273 iface.method43(); 274 iface.method44(); 275 iface.method45(); 276 iface.method46(); 277 iface.method47(); 278 iface.method48(); 279 iface.method49(); 280 iface.method50(); 281 iface.method51(); 282 iface.method52(); 283 iface.method53(); 284 iface.method54(); 285 iface.method55(); 286 iface.method56(); 287 iface.method57(); 288 iface.method58(); 289 iface.method59(); 290 iface.method60(); 291 iface.method61(); 292 iface.method62(); 293 iface.method63(); 294 iface.method64(); 295 iface.method65(); 296 iface.method66(); 297 iface.method67(); 298 iface.method68(); 299 iface.method69(); 300 iface.method70(); 301 iface.method71(); 302 iface.method72(); 303 iface.method73(); 304 iface.method74(); 305 iface.method75(); 306 iface.method76(); 307 iface.method77(); 308 iface.method78(); 309 iface.method79(); 310 } 311 $noinline$invokeConflictMethod(Constructor<?> constructor)312 private static void $noinline$invokeConflictMethod(Constructor<?> constructor) 313 throws Exception { 314 ClassLoader loader = (ClassLoader) constructor.newInstance( 315 DEX_FILE, LIBRARY_SEARCH_PATH, ClassLoader.getSystemClassLoader()); 316 Class<?> impl = loader.loadClass("ConflictImpl"); 317 ConflictIface iface = (ConflictIface) impl.newInstance(); 318 $noinline$callAllMethods(iface); 319 } 320 testConflictMethod(Constructor<?> constructor)321 private static void testConflictMethod(Constructor<?> constructor) throws Exception { 322 // Load and unload a few class loaders to force re-use of the native memory where we 323 // used to allocate the conflict table. 324 for (int i = 0; i < 2; i++) { 325 $noinline$invokeConflictMethod(constructor); 326 doUnloading(); 327 } 328 Class<?> impl = Class.forName("ConflictSuper"); 329 ConflictIface iface = (ConflictIface) impl.newInstance(); 330 $noinline$callAllMethods(iface); 331 } 332 testCopiedMethodInStackTrace(Constructor<?> constructor)333 private static void testCopiedMethodInStackTrace(Constructor<?> constructor) throws Exception { 334 Throwable t = $noinline$createStackTraceWithCopiedMethod(constructor); 335 doUnloading(); 336 assertStackTraceContains(t, "Iface", "invokeRun"); 337 } 338 $noinline$createStackTraceWithCopiedMethod(Constructor<?> constructor)339 private static Throwable $noinline$createStackTraceWithCopiedMethod(Constructor<?> constructor) 340 throws Exception { 341 ClassLoader loader = (ClassLoader) constructor.newInstance( 342 DEX_FILE, LIBRARY_SEARCH_PATH, Main.class.getClassLoader()); 343 Iface impl = (Iface) loader.loadClass("Impl").newInstance(); 344 Runnable throwingRunnable = new Runnable() { 345 public void run() { 346 throw new Error(); 347 } 348 }; 349 try { 350 impl.invokeRun(throwingRunnable); 351 System.out.println("UNREACHABLE"); 352 return null; 353 } catch (Error expected) { 354 return expected; 355 } 356 } 357 testCopiedBcpMethodInStackTrace()358 private static void testCopiedBcpMethodInStackTrace() { 359 Consumer<Object> consumer = new Consumer<Object>() { 360 public void accept(Object o) { 361 throw new Error(); 362 } 363 }; 364 Error err = null; 365 try { 366 Arrays.asList(new Object[] { new Object() }).iterator().forEachRemaining(consumer); 367 } catch (Error expected) { 368 err = expected; 369 } 370 assertStackTraceContains(err, "Main", "testCopiedBcpMethodInStackTrace"); 371 } 372 testCopiedAppImageMethodInStackTrace()373 private static void testCopiedAppImageMethodInStackTrace() throws Exception { 374 Iface limpl = (Iface) Class.forName("Impl2").newInstance(); 375 Runnable throwingRunnable = new Runnable() { 376 public void run() { 377 throw new Error(); 378 } 379 }; 380 Error err = null; 381 try { 382 limpl.invokeRun(throwingRunnable); 383 } catch (Error expected) { 384 err = expected; 385 } 386 assertStackTraceContains(err, "Main", "testCopiedAppImageMethodInStackTrace"); 387 } 388 setUpUnloadClassWeak(Constructor<?> constructor)389 private static WeakReference<Class> setUpUnloadClassWeak(Constructor<?> constructor) 390 throws Exception { 391 return new WeakReference<Class>(setUpUnloadClass(constructor)); 392 } 393 setUpUnloadLoader(Constructor<?> constructor, boolean waitForCompilation)394 private static WeakReference<ClassLoader> setUpUnloadLoader(Constructor<?> constructor, 395 boolean waitForCompilation) 396 throws Exception { 397 ClassLoader loader = (ClassLoader) constructor.newInstance( 398 DEX_FILE, LIBRARY_SEARCH_PATH, ClassLoader.getSystemClassLoader()); 399 Class<?> intHolder = loader.loadClass("IntHolder"); 400 Method setValue = intHolder.getDeclaredMethod("setValue", Integer.TYPE); 401 setValue.invoke(intHolder, 2); 402 if (waitForCompilation) { 403 waitForCompilation(intHolder); 404 } 405 return new WeakReference(loader); 406 } 407 waitForCompilation(Class<?> intHolder)408 private static void waitForCompilation(Class<?> intHolder) throws Exception { 409 // Load the native library so that we can call waitForCompilation. 410 Method loadLibrary = intHolder.getDeclaredMethod("loadLibrary", String.class); 411 loadLibrary.invoke(intHolder, nativeLibraryName); 412 // Wait for JIT compilation to finish since the async threads may prevent unloading. 413 Method waitForCompilation = intHolder.getDeclaredMethod("waitForCompilation"); 414 waitForCompilation.invoke(intHolder); 415 } 416 setUpLoadLibrary(Constructor<?> constructor)417 private static WeakReference<ClassLoader> setUpLoadLibrary(Constructor<?> constructor) 418 throws Exception { 419 ClassLoader loader = (ClassLoader) constructor.newInstance( 420 DEX_FILE, LIBRARY_SEARCH_PATH, ClassLoader.getSystemClassLoader()); 421 Class<?> intHolder = loader.loadClass("IntHolder"); 422 Method loadLibrary = intHolder.getDeclaredMethod("loadLibrary", String.class); 423 loadLibrary.invoke(intHolder, nativeLibraryName); 424 waitForCompilation(intHolder); 425 return new WeakReference(loader); 426 } 427 getPid()428 private static int getPid() throws Exception { 429 return Integer.parseInt(new File("/proc/self").getCanonicalFile().getName()); 430 } 431 stopJit()432 public static native void stopJit(); startJit()433 public static native void startJit(); 434 } 435