• 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.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