/* * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import java.io.File; import java.lang.ref.WeakReference; import java.lang.reflect.Method; import java.lang.reflect.InvocationTargetException; public class Main { public static volatile boolean quit = false; public static final boolean DEBUG = false; private static final boolean WRITE_HPROF_DATA = false; private static final int TEST_TIME = 10; private static final String OUTPUT_FILE = "gc-thrash.hprof"; public static void main(String[] args) { // dump heap before System.out.println("Running (" + TEST_TIME + " seconds) ..."); runTests(); Method dumpHprofDataMethod = null; String dumpFile = null; if (WRITE_HPROF_DATA) { dumpHprofDataMethod = getDumpHprofDataMethod(); if (dumpHprofDataMethod != null) { dumpFile = getDumpFileName(); System.out.println("Sending output to " + dumpFile); } } System.gc(); System.runFinalization(); System.gc(); if (WRITE_HPROF_DATA && dumpHprofDataMethod != null) { try { dumpHprofDataMethod.invoke(null, dumpFile); } catch (IllegalAccessException iae) { System.out.println(iae); } catch (InvocationTargetException ite) { System.out.println(ite); } } System.out.println("Done."); } /** * Finds VMDebug.dumpHprofData() through reflection. In the reference * implementation this will not be available. * * @return the reflection object, or null if the method can't be found */ private static Method getDumpHprofDataMethod() { ClassLoader myLoader = Main.class.getClassLoader(); Class vmdClass; try { vmdClass = myLoader.loadClass("dalvik.system.VMDebug"); } catch (ClassNotFoundException cnfe) { return null; } Method meth; try { meth = vmdClass.getMethod("dumpHprofData", String.class); } catch (NoSuchMethodException nsme) { System.out.println("Found VMDebug but not dumpHprofData method"); return null; } return meth; } private static String getDumpFileName() { File tmpDir = new File("/tmp"); if (tmpDir.exists() && tmpDir.isDirectory()) { return "/tmp/" + OUTPUT_FILE; } File sdcard = new File("/sdcard"); if (sdcard.exists() && sdcard.isDirectory()) { return "/sdcard/" + OUTPUT_FILE; } return null; } /** * Run the various tests for a set period. */ public static void runTests() { Robin robin = new Robin(); Deep deep = new Deep(); Large large = new Large(); /* start all threads */ robin.start(); deep.start(); large.start(); /* let everybody run for 10 seconds */ sleep(TEST_TIME * 1000); quit = true; try { /* wait for all threads to stop */ robin.join(); deep.join(); large.join(); } catch (InterruptedException ie) { System.out.println("join was interrupted"); } } /** * Sleeps for the "ms" milliseconds. */ public static void sleep(int ms) { try { Thread.sleep(ms); } catch (InterruptedException ie) { System.out.println("sleep was interrupted"); } } /** * Sleeps briefly, allowing other threads some CPU time to get started. */ public static void startupDelay() { sleep(500); } } /** * Allocates useless objects and holds on to several of them. * * Uses a single large array of references, replaced repeatedly in round-robin * order. */ class Robin extends Thread { private static final int ARRAY_SIZE = 40960; int sleepCount = 0; public void run() { Main.startupDelay(); String strings[] = new String[ARRAY_SIZE]; int idx = 0; while (!Main.quit) { strings[idx] = makeString(idx); if (idx % (ARRAY_SIZE / 4) == 0) { Main.sleep(400); sleepCount++; } idx = (idx + 1) % ARRAY_SIZE; } if (Main.DEBUG) System.out.println("Robin: sleepCount=" + sleepCount); } private String makeString(int val) { try { return new String("Robin" + val); } catch (OutOfMemoryError e) { return null; } } } /** * Allocates useless objects in recursive calls. */ class Deep extends Thread { private static final int MAX_DEPTH = 50; private static String strong[] = new String[MAX_DEPTH]; private static WeakReference weak[] = new WeakReference[MAX_DEPTH]; public void run() { int iter = 0; boolean once = false; Main.startupDelay(); while (!Main.quit) { dive(0, iter); once = true; iter += MAX_DEPTH; } if (!once) { System.out.println("not even once?"); return; } checkStringReferences(); /* * Wipe "strong", do a GC, see if "weak" got collected. */ for (int i = 0; i < MAX_DEPTH; i++) strong[i] = null; Runtime.getRuntime().gc(); for (int i = 0; i < MAX_DEPTH; i++) { if (weak[i].get() != null) { System.out.println("Deep: weak still has " + i); } } if (Main.DEBUG) System.out.println("Deep: iters=" + iter / MAX_DEPTH); } /** * Check the results of the last trip through. Everything in * "weak" should be matched in "strong", and the two should be * equivalent (object-wise, not just string-equality-wise). * * We do that check in a separate method to avoid retaining these * String references in local DEX registers. In interpreter mode, * they would retain these references until the end of the method * or until they are updated to another value. */ private static void checkStringReferences() { for (int i = 0; i < MAX_DEPTH; i++) { if (strong[i] != weak[i].get()) { System.out.println("Deep: " + i + " strong=" + strong[i] + ", weak=" + weak[i].get()); } } } /** * Recursively dive down, setting one or more local variables. * * We pad the stack out with locals, attempting to create a mix of * valid and invalid references on the stack. */ private String dive(int depth, int iteration) { try { String str0; String str1; String str2; String str3; String str4; String str5; String str6; String str7; String funStr = ""; switch (iteration % 8) { case 0: funStr = str0 = makeString(iteration); break; case 1: funStr = str1 = makeString(iteration); break; case 2: funStr = str2 = makeString(iteration); break; case 3: funStr = str3 = makeString(iteration); break; case 4: funStr = str4 = makeString(iteration); break; case 5: funStr = str5 = makeString(iteration); break; case 6: funStr = str6 = makeString(iteration); break; case 7: funStr = str7 = makeString(iteration); break; } weak[depth] = new WeakReference(funStr); strong[depth] = funStr; if (depth+1 < MAX_DEPTH) dive(depth+1, iteration+1); else Main.sleep(100); return funStr; } catch (OutOfMemoryError e) { // Silently ignore OOME since gc stress mode causes them to occur but shouldn't be a // test failure. } return ""; } private String makeString(int val) { try { return new String("Deep" + val); } catch (OutOfMemoryError e) { return null; } } } /** * Allocates large useless objects. */ class Large extends Thread { public void run() { byte[] chunk; int count = 0; int sleepCount = 0; Main.startupDelay(); while (!Main.quit) { try { chunk = new byte[100000]; pretendToUse(chunk); count++; if ((count % 500) == 0) { Main.sleep(400); sleepCount++; } } catch (OutOfMemoryError e) { } } if (Main.DEBUG) System.out.println("Large: sleepCount=" + sleepCount); } public void pretendToUse(byte[] chunk) {} }