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 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 */ 160 class 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 */ 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 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 */ 332 class 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