jeffhao | 5d1ac92 | 2011-09-29 17:41:15 -0700 | [diff] [blame] | 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) { |
Mathieu Chartier | cecc2d9 | 2014-10-13 11:45:52 -0700 | [diff] [blame] | 186 | try { |
| 187 | return new String("Robin" + val); |
| 188 | } catch (OutOfMemoryError e) { |
| 189 | return null; |
| 190 | } |
jeffhao | 5d1ac92 | 2011-09-29 17:41:15 -0700 | [diff] [blame] | 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 | /* |
| 222 | * Check the results of the last trip through. Everything in |
| 223 | * "weak" should be matched in "strong", and the two should be |
| 224 | * equivalent (object-wise, not just string-equality-wise). |
| 225 | */ |
| 226 | for (int i = 0; i < MAX_DEPTH; i++) { |
| 227 | if (strong[i] != weak[i].get()) { |
| 228 | System.err.println("Deep: " + i + " strong=" + strong[i] + |
| 229 | ", weak=" + weak[i].get()); |
| 230 | } |
| 231 | } |
| 232 | |
| 233 | /* |
| 234 | * Wipe "strong", do a GC, see if "weak" got collected. |
| 235 | */ |
| 236 | for (int i = 0; i < MAX_DEPTH; i++) |
| 237 | strong[i] = null; |
| 238 | |
Mathieu Chartier | 7befd0e | 2014-02-03 17:48:41 -0800 | [diff] [blame] | 239 | Runtime.getRuntime().gc(); |
jeffhao | 5d1ac92 | 2011-09-29 17:41:15 -0700 | [diff] [blame] | 240 | |
| 241 | for (int i = 0; i < MAX_DEPTH; i++) { |
| 242 | if (weak[i].get() != null) { |
| 243 | System.err.println("Deep: weak still has " + i); |
| 244 | } |
| 245 | } |
| 246 | |
| 247 | if (Main.DEBUG) |
| 248 | System.out.println("Deep: iters=" + iter / MAX_DEPTH); |
| 249 | } |
| 250 | |
| 251 | /** |
| 252 | * Recursively dive down, setting one or more local variables. |
| 253 | * |
| 254 | * We pad the stack out with locals, attempting to create a mix of |
| 255 | * valid and invalid references on the stack. |
| 256 | */ |
| 257 | private String dive(int depth, int iteration) { |
Mathieu Chartier | 9dc0ced | 2014-10-16 10:01:39 -0700 | [diff] [blame] | 258 | try { |
| 259 | String str0; |
| 260 | String str1; |
| 261 | String str2; |
| 262 | String str3; |
| 263 | String str4; |
| 264 | String str5; |
| 265 | String str6; |
| 266 | String str7; |
| 267 | String funStr = ""; |
| 268 | switch (iteration % 8) { |
| 269 | case 0: |
| 270 | funStr = str0 = makeString(iteration); |
| 271 | break; |
| 272 | case 1: |
| 273 | funStr = str1 = makeString(iteration); |
| 274 | break; |
| 275 | case 2: |
| 276 | funStr = str2 = makeString(iteration); |
| 277 | break; |
| 278 | case 3: |
| 279 | funStr = str3 = makeString(iteration); |
| 280 | break; |
| 281 | case 4: |
| 282 | funStr = str4 = makeString(iteration); |
| 283 | break; |
| 284 | case 5: |
| 285 | funStr = str5 = makeString(iteration); |
| 286 | break; |
| 287 | case 6: |
| 288 | funStr = str6 = makeString(iteration); |
| 289 | break; |
| 290 | case 7: |
| 291 | funStr = str7 = makeString(iteration); |
| 292 | break; |
| 293 | } |
jeffhao | 5d1ac92 | 2011-09-29 17:41:15 -0700 | [diff] [blame] | 294 | |
Mathieu Chartier | 9dc0ced | 2014-10-16 10:01:39 -0700 | [diff] [blame] | 295 | weak[depth] = new WeakReference(funStr); |
Andreas Gampe | 2f6046e | 2015-02-02 17:44:31 -0800 | [diff] [blame^] | 296 | strong[depth] = funStr; |
Mathieu Chartier | 9dc0ced | 2014-10-16 10:01:39 -0700 | [diff] [blame] | 297 | if (depth+1 < MAX_DEPTH) |
| 298 | dive(depth+1, iteration+1); |
| 299 | else |
| 300 | Main.sleep(100); |
| 301 | return funStr; |
| 302 | } catch (OutOfMemoryError e) { |
| 303 | // Silently ignore OOME since gc stress mode causes them to occur but shouldn't be a |
| 304 | // test failure. |
jeffhao | 5d1ac92 | 2011-09-29 17:41:15 -0700 | [diff] [blame] | 305 | } |
Mathieu Chartier | 9dc0ced | 2014-10-16 10:01:39 -0700 | [diff] [blame] | 306 | return ""; |
jeffhao | 5d1ac92 | 2011-09-29 17:41:15 -0700 | [diff] [blame] | 307 | } |
| 308 | |
| 309 | private String makeString(int val) { |
Mathieu Chartier | cecc2d9 | 2014-10-13 11:45:52 -0700 | [diff] [blame] | 310 | try { |
| 311 | return new String("Deep" + val); |
| 312 | } catch (OutOfMemoryError e) { |
| 313 | return null; |
| 314 | } |
jeffhao | 5d1ac92 | 2011-09-29 17:41:15 -0700 | [diff] [blame] | 315 | } |
| 316 | } |
| 317 | |
| 318 | |
| 319 | /** |
| 320 | * Allocates large useless objects. |
| 321 | */ |
| 322 | class Large extends Thread { |
| 323 | public void run() { |
| 324 | byte[] chunk; |
| 325 | int count = 0; |
| 326 | int sleepCount = 0; |
| 327 | |
| 328 | Main.startupDelay(); |
| 329 | |
| 330 | while (!Main.quit) { |
Mathieu Chartier | cecc2d9 | 2014-10-13 11:45:52 -0700 | [diff] [blame] | 331 | try { |
| 332 | chunk = new byte[100000]; |
| 333 | pretendToUse(chunk); |
jeffhao | 5d1ac92 | 2011-09-29 17:41:15 -0700 | [diff] [blame] | 334 | |
Mathieu Chartier | cecc2d9 | 2014-10-13 11:45:52 -0700 | [diff] [blame] | 335 | count++; |
| 336 | if ((count % 500) == 0) { |
| 337 | Main.sleep(400); |
| 338 | sleepCount++; |
| 339 | } |
| 340 | } catch (OutOfMemoryError e) { |
jeffhao | 5d1ac92 | 2011-09-29 17:41:15 -0700 | [diff] [blame] | 341 | } |
| 342 | } |
| 343 | |
| 344 | if (Main.DEBUG) |
| 345 | System.out.println("Large: sleepCount=" + sleepCount); |
| 346 | } |
| 347 | |
| 348 | public void pretendToUse(byte[] chunk) {} |
| 349 | } |