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) { |
Kevin Brodsky | f6c66c3 | 2015-12-17 14:13:00 +0000 | [diff] [blame] | 55 | System.out.println(iae); |
jeffhao | 5d1ac92 | 2011-09-29 17:41:15 -0700 | [diff] [blame] | 56 | } catch (InvocationTargetException ite) { |
Kevin Brodsky | f6c66c3 | 2015-12-17 14:13:00 +0000 | [diff] [blame] | 57 | System.out.println(ite); |
jeffhao | 5d1ac92 | 2011-09-29 17:41:15 -0700 | [diff] [blame] | 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(); |
Andreas Gampe | 166aaee | 2016-07-18 08:27:23 -0700 | [diff] [blame] | 72 | Class<?> vmdClass; |
jeffhao | 5d1ac92 | 2011-09-29 17:41:15 -0700 | [diff] [blame] | 73 | try { |
| 74 | vmdClass = myLoader.loadClass("dalvik.system.VMDebug"); |
| 75 | } catch (ClassNotFoundException cnfe) { |
| 76 | return null; |
| 77 | } |
| 78 | |
| 79 | Method meth; |
| 80 | try { |
Andreas Gampe | 166aaee | 2016-07-18 08:27:23 -0700 | [diff] [blame] | 81 | meth = vmdClass.getMethod("dumpHprofData", String.class); |
jeffhao | 5d1ac92 | 2011-09-29 17:41:15 -0700 | [diff] [blame] | 82 | } catch (NoSuchMethodException nsme) { |
Kevin Brodsky | f6c66c3 | 2015-12-17 14:13:00 +0000 | [diff] [blame] | 83 | System.out.println("Found VMDebug but not dumpHprofData method"); |
jeffhao | 5d1ac92 | 2011-09-29 17:41:15 -0700 | [diff] [blame] | 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) { |
Kevin Brodsky | f6c66c3 | 2015-12-17 14:13:00 +0000 | [diff] [blame] | 129 | System.out.println("join was interrupted"); |
jeffhao | 5d1ac92 | 2011-09-29 17:41:15 -0700 | [diff] [blame] | 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) { |
Kevin Brodsky | f6c66c3 | 2015-12-17 14:13:00 +0000 | [diff] [blame] | 140 | System.out.println("sleep was interrupted"); |
jeffhao | 5d1ac92 | 2011-09-29 17:41:15 -0700 | [diff] [blame] | 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) { |
Mathieu Chartier | cecc2d9 | 2014-10-13 11:45:52 -0700 | [diff] [blame] | 185 | try { |
| 186 | return new String("Robin" + val); |
| 187 | } catch (OutOfMemoryError e) { |
| 188 | return null; |
| 189 | } |
jeffhao | 5d1ac92 | 2011-09-29 17:41:15 -0700 | [diff] [blame] | 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) { |
Kevin Brodsky | f6c66c3 | 2015-12-17 14:13:00 +0000 | [diff] [blame] | 216 | System.out.println("not even once?"); |
jeffhao | 5d1ac92 | 2011-09-29 17:41:15 -0700 | [diff] [blame] | 217 | return; |
| 218 | } |
| 219 | |
Sebastien Hertz | 19ac027 | 2015-02-24 17:39:50 +0100 | [diff] [blame] | 220 | checkStringReferences(); |
jeffhao | 5d1ac92 | 2011-09-29 17:41:15 -0700 | [diff] [blame] | 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 | |
Mathieu Chartier | 7befd0e | 2014-02-03 17:48:41 -0800 | [diff] [blame] | 228 | Runtime.getRuntime().gc(); |
jeffhao | 5d1ac92 | 2011-09-29 17:41:15 -0700 | [diff] [blame] | 229 | |
| 230 | for (int i = 0; i < MAX_DEPTH; i++) { |
| 231 | if (weak[i].get() != null) { |
Kevin Brodsky | f6c66c3 | 2015-12-17 14:13:00 +0000 | [diff] [blame] | 232 | System.out.println("Deep: weak still has " + i); |
jeffhao | 5d1ac92 | 2011-09-29 17:41:15 -0700 | [diff] [blame] | 233 | } |
| 234 | } |
| 235 | |
| 236 | if (Main.DEBUG) |
| 237 | System.out.println("Deep: iters=" + iter / MAX_DEPTH); |
| 238 | } |
| 239 | |
Sebastien Hertz | 19ac027 | 2015-02-24 17:39:50 +0100 | [diff] [blame] | 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()) { |
Kevin Brodsky | f6c66c3 | 2015-12-17 14:13:00 +0000 | [diff] [blame] | 254 | System.out.println("Deep: " + i + " strong=" + strong[i] + |
Sebastien Hertz | 19ac027 | 2015-02-24 17:39:50 +0100 | [diff] [blame] | 255 | ", weak=" + weak[i].get()); |
| 256 | } |
| 257 | } |
| 258 | } |
| 259 | |
jeffhao | 5d1ac92 | 2011-09-29 17:41:15 -0700 | [diff] [blame] | 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) { |
Mathieu Chartier | 9dc0ced | 2014-10-16 10:01:39 -0700 | [diff] [blame] | 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 | } |
jeffhao | 5d1ac92 | 2011-09-29 17:41:15 -0700 | [diff] [blame] | 303 | |
Nicolas Geoffray | ed8b53d | 2015-02-03 08:48:27 +0000 | [diff] [blame] | 304 | weak[depth] = new WeakReference(funStr); |
Nicolas Geoffray | 67f65ea | 2015-02-03 10:10:52 +0000 | [diff] [blame] | 305 | strong[depth] = funStr; |
Mathieu Chartier | 9dc0ced | 2014-10-16 10:01:39 -0700 | [diff] [blame] | 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. |
jeffhao | 5d1ac92 | 2011-09-29 17:41:15 -0700 | [diff] [blame] | 314 | } |
Mathieu Chartier | 9dc0ced | 2014-10-16 10:01:39 -0700 | [diff] [blame] | 315 | return ""; |
jeffhao | 5d1ac92 | 2011-09-29 17:41:15 -0700 | [diff] [blame] | 316 | } |
| 317 | |
| 318 | private String makeString(int val) { |
Mathieu Chartier | cecc2d9 | 2014-10-13 11:45:52 -0700 | [diff] [blame] | 319 | try { |
| 320 | return new String("Deep" + val); |
| 321 | } catch (OutOfMemoryError e) { |
| 322 | return null; |
| 323 | } |
jeffhao | 5d1ac92 | 2011-09-29 17:41:15 -0700 | [diff] [blame] | 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) { |
Mathieu Chartier | cecc2d9 | 2014-10-13 11:45:52 -0700 | [diff] [blame] | 340 | try { |
| 341 | chunk = new byte[100000]; |
| 342 | pretendToUse(chunk); |
jeffhao | 5d1ac92 | 2011-09-29 17:41:15 -0700 | [diff] [blame] | 343 | |
Mathieu Chartier | cecc2d9 | 2014-10-13 11:45:52 -0700 | [diff] [blame] | 344 | count++; |
| 345 | if ((count % 500) == 0) { |
| 346 | Main.sleep(400); |
| 347 | sleepCount++; |
| 348 | } |
| 349 | } catch (OutOfMemoryError e) { |
jeffhao | 5d1ac92 | 2011-09-29 17:41:15 -0700 | [diff] [blame] | 350 | } |
| 351 | } |
| 352 | |
| 353 | if (Main.DEBUG) |
| 354 | System.out.println("Large: sleepCount=" + sleepCount); |
| 355 | } |
| 356 | |
| 357 | public void pretendToUse(byte[] chunk) {} |
| 358 | } |