blob: 5165df7bd3edee51e4da37990917cd7abeb85d70 [file] [log] [blame]
/*
* 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 = 61;
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) {}
}