Main.java revision b6e20ae17d0881a66c22532e4152ce6779454a92
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
17import java.io.BufferedReader;
18import java.io.File;
19import java.io.FileReader;
20import java.lang.ref.WeakReference;
21import java.lang.reflect.Constructor;
22import java.lang.reflect.Method;
23
24public class Main {
25    static final String DEX_FILE = System.getenv("DEX_LOCATION") + "/141-class-unload-ex.jar";
26    static String nativeLibraryName;
27
28    public static void main(String[] args) throws Exception {
29        nativeLibraryName = args[0];
30        Class pathClassLoader = Class.forName("dalvik.system.PathClassLoader");
31        if (pathClassLoader == null) {
32            throw new AssertionError("Couldn't find path class loader class");
33        }
34        Constructor constructor =
35            pathClassLoader.getDeclaredConstructor(String.class, ClassLoader.class);
36        try {
37            testUnloadClass(constructor);
38            testUnloadLoader(constructor);
39            // Test that we don't unload if we have a Method keeping the class live.
40            testNoUnloadInvoke(constructor);
41            // Test that we don't unload if we have an instance.
42            testNoUnloadInstance(constructor);
43            // Test JNI_OnLoad and JNI_OnUnload.
44            testLoadAndUnloadLibrary(constructor);
45            // Test that stack traces keep the classes live.
46            testStackTrace(constructor);
47            // Stress test to make sure we dont leak memory.
48            stressTest(constructor);
49            // Test that the oat files are unloaded.
50            testOatFilesUnloaded(getPid());
51        } catch (Exception e) {
52            System.out.println(e);
53        }
54    }
55
56    private static void testOatFilesUnloaded(int pid) throws Exception {
57        BufferedReader reader = new BufferedReader(new FileReader ("/proc/" + pid + "/maps"));
58        String line;
59        int count = 0;
60        Runtime.getRuntime().gc();
61        System.runFinalization();
62        while ((line = reader.readLine()) != null) {
63            if (line.contains("@141-class-unload-ex.jar")) {
64                System.out.println(line);
65                ++count;
66            }
67        }
68        System.out.println("Number of loaded unload-ex maps " + count);
69    }
70
71    private static void stressTest(Constructor constructor) throws Exception {
72        for (int i = 0; i <= 100; ++i) {
73            setUpUnloadLoader(constructor, false);
74            if (i % 10 == 0) {
75                Runtime.getRuntime().gc();
76            }
77        }
78    }
79
80    private static void testUnloadClass(Constructor constructor) throws Exception {
81        WeakReference<Class> klass = setUpUnloadClass(constructor);
82        // No strong references to class loader, should get unloaded.
83        Runtime.getRuntime().gc();
84        WeakReference<Class> klass2 = setUpUnloadClass(constructor);
85        Runtime.getRuntime().gc();
86        // If the weak reference is cleared, then it was unloaded.
87        System.out.println(klass.get());
88        System.out.println(klass2.get());
89    }
90
91    private static void testUnloadLoader(Constructor constructor)
92        throws Exception {
93      WeakReference<ClassLoader> loader = setUpUnloadLoader(constructor, true);
94      // No strong references to class loader, should get unloaded.
95      Runtime.getRuntime().gc();
96      // If the weak reference is cleared, then it was unloaded.
97      System.out.println(loader.get());
98    }
99
100    private static void testStackTrace(Constructor constructor) throws Exception {
101        WeakReference<Class> klass = setUpUnloadClass(constructor);
102        Method stackTraceMethod = klass.get().getDeclaredMethod("generateStackTrace");
103        Throwable throwable = (Throwable) stackTraceMethod.invoke(klass.get());
104        stackTraceMethod = null;
105        Runtime.getRuntime().gc();
106        boolean isNull = klass.get() == null;
107        System.out.println("class null " + isNull + " " + throwable.getMessage());
108    }
109
110    private static void testLoadAndUnloadLibrary(Constructor constructor) throws Exception {
111        WeakReference<ClassLoader> loader = setUpLoadLibrary(constructor);
112        // No strong references to class loader, should get unloaded.
113        Runtime.getRuntime().gc();
114        // If the weak reference is cleared, then it was unloaded.
115        System.out.println(loader.get());
116    }
117
118    private static void testNoUnloadInvoke(Constructor constructor) throws Exception {
119        WeakReference<ClassLoader> loader =
120            new WeakReference((ClassLoader) constructor.newInstance(
121                DEX_FILE, ClassLoader.getSystemClassLoader()));
122        WeakReference<Class> intHolder = new WeakReference(loader.get().loadClass("IntHolder"));
123        intHolder.get().getDeclaredMethod("runGC").invoke(intHolder.get());
124        boolean isNull = loader.get() == null;
125        System.out.println("loader null " + isNull);
126    }
127
128    private static void testNoUnloadInstance(Constructor constructor) throws Exception {
129        WeakReference<ClassLoader> loader =
130            new WeakReference((ClassLoader) constructor.newInstance(
131                DEX_FILE, ClassLoader.getSystemClassLoader()));
132        WeakReference<Class> intHolder = new WeakReference(loader.get().loadClass("IntHolder"));
133        Object o = intHolder.get().newInstance();
134        Runtime.getRuntime().gc();
135        boolean isNull = loader.get() == null;
136        System.out.println("loader null " + isNull);
137    }
138
139    private static WeakReference<Class> setUpUnloadClass(Constructor constructor) throws Exception {
140        ClassLoader loader = (ClassLoader) constructor.newInstance(
141            DEX_FILE, ClassLoader.getSystemClassLoader());
142        Class intHolder = loader.loadClass("IntHolder");
143        Method getValue = intHolder.getDeclaredMethod("getValue");
144        Method setValue = intHolder.getDeclaredMethod("setValue", Integer.TYPE);
145        // Make sure we don't accidentally preserve the value in the int holder, the class
146        // initializer should be re-run.
147        System.out.println((int) getValue.invoke(intHolder));
148        setValue.invoke(intHolder, 2);
149        System.out.println((int) getValue.invoke(intHolder));
150        waitForCompilation(intHolder);
151        return new WeakReference(intHolder);
152    }
153
154    private static WeakReference<ClassLoader> setUpUnloadLoader(Constructor constructor,
155                                                                boolean waitForCompilation)
156        throws Exception {
157        ClassLoader loader = (ClassLoader) constructor.newInstance(
158            DEX_FILE, ClassLoader.getSystemClassLoader());
159        Class intHolder = loader.loadClass("IntHolder");
160        Method setValue = intHolder.getDeclaredMethod("setValue", Integer.TYPE);
161        setValue.invoke(intHolder, 2);
162        if (waitForCompilation) {
163            waitForCompilation(intHolder);
164        }
165        return new WeakReference(loader);
166    }
167
168    private static void waitForCompilation(Class intHolder) throws Exception {
169      // Load the native library so that we can call waitForCompilation.
170      Method loadLibrary = intHolder.getDeclaredMethod("loadLibrary", String.class);
171      loadLibrary.invoke(intHolder, nativeLibraryName);
172      // Wait for JIT compilation to finish since the async threads may prevent unloading.
173      Method waitForCompilation = intHolder.getDeclaredMethod("waitForCompilation");
174      waitForCompilation.invoke(intHolder);
175    }
176
177    private static WeakReference<ClassLoader> setUpLoadLibrary(Constructor constructor)
178        throws Exception {
179        ClassLoader loader = (ClassLoader) constructor.newInstance(
180            DEX_FILE, ClassLoader.getSystemClassLoader());
181        Class intHolder = loader.loadClass("IntHolder");
182        Method loadLibrary = intHolder.getDeclaredMethod("loadLibrary", String.class);
183        loadLibrary.invoke(intHolder, nativeLibraryName);
184        waitForCompilation(intHolder);
185        return new WeakReference(loader);
186    }
187
188    private static int getPid() throws Exception {
189      return Integer.parseInt(new File("/proc/self").getCanonicalFile().getName());
190    }
191}
192