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