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