• Home
  • History
  • Annotate
  • 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