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 
17 import java.io.File;
18 import java.lang.ref.WeakReference;
19 import java.lang.reflect.Method;
20 import java.lang.reflect.InvocationTargetException;
21 
22 public 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 
main(String[] args)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      */
getDumpHprofDataMethod()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 
getDumpFileName()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      */
runTests()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      */
sleep(int ms)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      */
startupDelay()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  */
159 class Robin extends Thread {
160     private static final int ARRAY_SIZE = 40960;
161     int sleepCount = 0;
162 
run()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 
makeString(int val)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  */
197 class Deep extends Thread {
198     private static final int MAX_DEPTH = 50;
199 
200     private static String strong[] = new String[MAX_DEPTH];
201     private static WeakReference weak[] = new WeakReference[MAX_DEPTH];
202 
run()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      */
checkStringReferences()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      */
dive(int depth, int iteration)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 
makeString(int val)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  */
331 class Large extends Thread {
run()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 
pretendToUse(byte[] chunk)357     public void pretendToUse(byte[] chunk) {}
358 }
359