1/*
2 * Copyright (C) 2009 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.File;
18import java.lang.ref.WeakReference;
19import java.lang.reflect.Method;
20import java.lang.reflect.InvocationTargetException;
21
22public class Main {
23    public static volatile boolean quit = false;
24    public static final boolean DEBUG = false;
25
26    private static final boolean WRITE_HPROF_DATA = false;
27    private static final int TEST_TIME = 10;
28    private static final String OUTPUT_FILE = "gc-thrash.hprof";
29
30    public static void main(String[] args) {
31        // dump heap before
32
33        System.out.println("Running (" + TEST_TIME + " seconds) ...");
34        runTests();
35
36        Method dumpHprofDataMethod = null;
37        String dumpFile = null;
38
39        if (WRITE_HPROF_DATA) {
40            dumpHprofDataMethod = getDumpHprofDataMethod();
41            if (dumpHprofDataMethod != null) {
42                dumpFile = getDumpFileName();
43                System.out.println("Sending output to " + dumpFile);
44            }
45        }
46
47        System.gc();
48        System.runFinalization();
49        System.gc();
50
51        if (WRITE_HPROF_DATA && dumpHprofDataMethod != null) {
52            try {
53                dumpHprofDataMethod.invoke(null, dumpFile);
54            } catch (IllegalAccessException iae) {
55                System.err.println(iae);
56            } catch (InvocationTargetException ite) {
57                System.err.println(ite);
58            }
59        }
60
61        System.out.println("Done.");
62    }
63
64    /**
65     * Finds VMDebug.dumpHprofData() through reflection.  In the reference
66     * implementation this will not be available.
67     *
68     * @return the reflection object, or null if the method can't be found
69     */
70    private static Method getDumpHprofDataMethod() {
71        ClassLoader myLoader = Main.class.getClassLoader();
72        Class vmdClass;
73        try {
74            vmdClass = myLoader.loadClass("dalvik.system.VMDebug");
75        } catch (ClassNotFoundException cnfe) {
76            return null;
77        }
78
79        Method meth;
80        try {
81            meth = vmdClass.getMethod("dumpHprofData",
82                    new Class[] { String.class });
83        } catch (NoSuchMethodException nsme) {
84            System.err.println("Found VMDebug but not dumpHprofData method");
85            return null;
86        }
87
88        return meth;
89    }
90
91    private static String getDumpFileName() {
92        File tmpDir = new File("/tmp");
93        if (tmpDir.exists() && tmpDir.isDirectory()) {
94            return "/tmp/" + OUTPUT_FILE;
95        }
96
97        File sdcard = new File("/sdcard");
98        if (sdcard.exists() && sdcard.isDirectory()) {
99            return "/sdcard/" + OUTPUT_FILE;
100        }
101
102        return null;
103    }
104
105
106    /**
107     * Run the various tests for a set period.
108     */
109    public static void runTests() {
110        Robin robin = new Robin();
111        Deep deep = new Deep();
112        Large large = new Large();
113
114        /* start all threads */
115        robin.start();
116        deep.start();
117        large.start();
118
119        /* let everybody run for 10 seconds */
120        sleep(TEST_TIME * 1000);
121
122        quit = true;
123
124        try {
125            /* wait for all threads to stop */
126            robin.join();
127            deep.join();
128            large.join();
129        } catch (InterruptedException ie) {
130            System.err.println("join was interrupted");
131        }
132    }
133
134    /**
135     * Sleeps for the "ms" milliseconds.
136     */
137    public static void sleep(int ms) {
138        try {
139            Thread.sleep(ms);
140        } catch (InterruptedException ie) {
141            System.err.println("sleep was interrupted");
142        }
143    }
144
145    /**
146     * Sleeps briefly, allowing other threads some CPU time to get started.
147     */
148    public static void startupDelay() {
149        sleep(500);
150    }
151}
152
153
154/**
155 * Allocates useless objects and holds on to several of them.
156 *
157 * Uses a single large array of references, replaced repeatedly in round-robin
158 * order.
159 */
160class Robin extends Thread {
161    private static final int ARRAY_SIZE = 40960;
162    int sleepCount = 0;
163
164    public void run() {
165        Main.startupDelay();
166
167        String strings[] = new String[ARRAY_SIZE];
168        int idx = 0;
169
170        while (!Main.quit) {
171            strings[idx] = makeString(idx);
172
173            if (idx % (ARRAY_SIZE / 4) == 0) {
174                Main.sleep(400);
175                sleepCount++;
176            }
177
178            idx = (idx + 1) % ARRAY_SIZE;
179        }
180
181        if (Main.DEBUG)
182            System.out.println("Robin: sleepCount=" + sleepCount);
183    }
184
185    private String makeString(int val) {
186        try {
187            return new String("Robin" + val);
188        } catch (OutOfMemoryError e) {
189            return null;
190        }
191    }
192}
193
194
195/**
196 * Allocates useless objects in recursive calls.
197 */
198class Deep extends Thread {
199    private static final int MAX_DEPTH = 61;
200
201    private static String strong[] = new String[MAX_DEPTH];
202    private static WeakReference weak[] = new WeakReference[MAX_DEPTH];
203
204    public void run() {
205        int iter = 0;
206        boolean once = false;
207
208        Main.startupDelay();
209
210        while (!Main.quit) {
211            dive(0, iter);
212            once = true;
213            iter += MAX_DEPTH;
214        }
215
216        if (!once) {
217            System.err.println("not even once?");
218            return;
219        }
220
221        checkStringReferences();
222
223        /*
224         * Wipe "strong", do a GC, see if "weak" got collected.
225         */
226        for (int i = 0; i < MAX_DEPTH; i++)
227            strong[i] = null;
228
229        Runtime.getRuntime().gc();
230
231        for (int i = 0; i < MAX_DEPTH; i++) {
232            if (weak[i].get() != null) {
233                System.err.println("Deep: weak still has " + i);
234            }
235        }
236
237        if (Main.DEBUG)
238            System.out.println("Deep: iters=" + iter / MAX_DEPTH);
239    }
240
241
242    /**
243     * Check the results of the last trip through.  Everything in
244     * "weak" should be matched in "strong", and the two should be
245     * equivalent (object-wise, not just string-equality-wise).
246     *
247     * We do that check in a separate method to avoid retaining these
248     * String references in local DEX registers. In interpreter mode,
249     * they would retain these references until the end of the method
250     * or until they are updated to another value.
251     */
252    private static void checkStringReferences() {
253      for (int i = 0; i < MAX_DEPTH; i++) {
254          if (strong[i] != weak[i].get()) {
255              System.err.println("Deep: " + i + " strong=" + strong[i] +
256                  ", weak=" + weak[i].get());
257          }
258      }
259    }
260
261    /**
262     * Recursively dive down, setting one or more local variables.
263     *
264     * We pad the stack out with locals, attempting to create a mix of
265     * valid and invalid references on the stack.
266     */
267    private String dive(int depth, int iteration) {
268        try {
269            String str0;
270            String str1;
271            String str2;
272            String str3;
273            String str4;
274            String str5;
275            String str6;
276            String str7;
277            String funStr = "";
278            switch (iteration % 8) {
279                case 0:
280                    funStr = str0 = makeString(iteration);
281                    break;
282                case 1:
283                    funStr = str1 = makeString(iteration);
284                    break;
285                case 2:
286                    funStr = str2 = makeString(iteration);
287                    break;
288                case 3:
289                    funStr = str3 = makeString(iteration);
290                    break;
291                case 4:
292                    funStr = str4 = makeString(iteration);
293                    break;
294                case 5:
295                    funStr = str5 = makeString(iteration);
296                    break;
297                case 6:
298                    funStr = str6 = makeString(iteration);
299                    break;
300                case 7:
301                    funStr = str7 = makeString(iteration);
302                    break;
303            }
304
305            weak[depth] = new WeakReference(funStr);
306            strong[depth] = funStr;
307            if (depth+1 < MAX_DEPTH)
308                dive(depth+1, iteration+1);
309            else
310                Main.sleep(100);
311            return funStr;
312        } catch (OutOfMemoryError e) {
313            // Silently ignore OOME since gc stress mode causes them to occur but shouldn't be a
314            // test failure.
315        }
316        return "";
317    }
318
319    private String makeString(int val) {
320        try {
321            return new String("Deep" + val);
322        } catch (OutOfMemoryError e) {
323            return null;
324        }
325    }
326}
327
328
329/**
330 * Allocates large useless objects.
331 */
332class Large extends Thread {
333    public void run() {
334        byte[] chunk;
335        int count = 0;
336        int sleepCount = 0;
337
338        Main.startupDelay();
339
340        while (!Main.quit) {
341            try {
342                chunk = new byte[100000];
343                pretendToUse(chunk);
344
345                count++;
346                if ((count % 500) == 0) {
347                    Main.sleep(400);
348                    sleepCount++;
349                }
350            } catch (OutOfMemoryError e) {
351            }
352        }
353
354        if (Main.DEBUG)
355            System.out.println("Large: sleepCount=" + sleepCount);
356    }
357
358    public void pretendToUse(byte[] chunk) {}
359}
360