• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.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      */
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",
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 
getDumpFileName()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      */
runTests()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      */
sleep(int ms)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      */
startupDelay()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  */
160 class Robin extends Thread {
161     private static final int ARRAY_SIZE = 40960;
162     int sleepCount = 0;
163 
run()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 
makeString(int val)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  */
198 class 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 
run()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      */
checkStringReferences()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      */
dive(int depth, int iteration)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 
makeString(int val)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  */
332 class Large extends Thread {
run()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 
pretendToUse(byte[] chunk)358     public void pretendToUse(byte[] chunk) {}
359 }
360