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 return new String("Robin" + val); 187 } 188 } 189 190 191 /** 192 * Allocates useless objects in recursive calls. 193 */ 194 class Deep extends Thread { 195 private static final int MAX_DEPTH = 61; 196 197 private static String strong[] = new String[MAX_DEPTH]; 198 private static WeakReference weak[] = new WeakReference[MAX_DEPTH]; 199 200 public void run() { 201 int iter = 0; 202 boolean once = false; 203 204 Main.startupDelay(); 205 206 while (!Main.quit) { 207 dive(0, iter); 208 once = true; 209 iter += MAX_DEPTH; 210 } 211 212 if (!once) { 213 System.err.println("not even once?"); 214 return; 215 } 216 217 /* 218 * Check the results of the last trip through. Everything in 219 * "weak" should be matched in "strong", and the two should be 220 * equivalent (object-wise, not just string-equality-wise). 221 */ 222 for (int i = 0; i < MAX_DEPTH; i++) { 223 if (strong[i] != weak[i].get()) { 224 System.err.println("Deep: " + i + " strong=" + strong[i] + 225 ", weak=" + weak[i].get()); 226 } 227 } 228 229 /* 230 * Wipe "strong", do a GC, see if "weak" got collected. 231 */ 232 for (int i = 0; i < MAX_DEPTH; i++) 233 strong[i] = null; 234 235 System.gc(); 236 237 for (int i = 0; i < MAX_DEPTH; i++) { 238 if (weak[i].get() != null) { 239 System.err.println("Deep: weak still has " + i); 240 } 241 } 242 243 if (Main.DEBUG) 244 System.out.println("Deep: iters=" + iter / MAX_DEPTH); 245 } 246 247 /** 248 * Recursively dive down, setting one or more local variables. 249 * 250 * We pad the stack out with locals, attempting to create a mix of 251 * valid and invalid references on the stack. 252 */ 253 private String dive(int depth, int iteration) { 254 String str0; 255 String str1; 256 String str2; 257 String str3; 258 String str4; 259 String str5; 260 String str6; 261 String str7; 262 String funStr; 263 264 funStr = ""; 265 266 switch (iteration % 8) { 267 case 0: 268 funStr = str0 = makeString(iteration); 269 break; 270 case 1: 271 funStr = str1 = makeString(iteration); 272 break; 273 case 2: 274 funStr = str2 = makeString(iteration); 275 break; 276 case 3: 277 funStr = str3 = makeString(iteration); 278 break; 279 case 4: 280 funStr = str4 = makeString(iteration); 281 break; 282 case 5: 283 funStr = str5 = makeString(iteration); 284 break; 285 case 6: 286 funStr = str6 = makeString(iteration); 287 break; 288 case 7: 289 funStr = str7 = makeString(iteration); 290 break; 291 } 292 293 strong[depth] = funStr; 294 weak[depth] = new WeakReference(funStr); 295 296 if (depth+1 < MAX_DEPTH) 297 dive(depth+1, iteration+1); 298 else 299 Main.sleep(100); 300 301 return funStr; 302 } 303 304 private String makeString(int val) { 305 return new String("Deep" + val); 306 } 307 } 308 309 310 /** 311 * Allocates large useless objects. 312 */ 313 class Large extends Thread { 314 public void run() { 315 byte[] chunk; 316 int count = 0; 317 int sleepCount = 0; 318 319 Main.startupDelay(); 320 321 while (!Main.quit) { 322 chunk = new byte[100000]; 323 pretendToUse(chunk); 324 325 count++; 326 if ((count % 500) == 0) { 327 Main.sleep(400); 328 sleepCount++; 329 } 330 } 331 332 if (Main.DEBUG) 333 System.out.println("Large: sleepCount=" + sleepCount); 334 } 335 336 public void pretendToUse(byte[] chunk) {} 337 } 338