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", String.class); 82 } catch (NoSuchMethodException nsme) { 83 System.err.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.err.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.err.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 */ 159 class 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 */ 197 class 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.err.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.err.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.err.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 */ 331 class 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