• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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