summaryrefslogtreecommitdiffstats
path: root/tests/AndroidTests/src/com/android/unit_tests/HeapTest.java
diff options
context:
space:
mode:
Diffstat (limited to 'tests/AndroidTests/src/com/android/unit_tests/HeapTest.java')
-rw-r--r--tests/AndroidTests/src/com/android/unit_tests/HeapTest.java631
1 files changed, 631 insertions, 0 deletions
diff --git a/tests/AndroidTests/src/com/android/unit_tests/HeapTest.java b/tests/AndroidTests/src/com/android/unit_tests/HeapTest.java
new file mode 100644
index 0000000..d21e6a3
--- /dev/null
+++ b/tests/AndroidTests/src/com/android/unit_tests/HeapTest.java
@@ -0,0 +1,631 @@
+/*
+ * Copyright (C) 2007 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.
+ */
+
+package com.android.unit_tests;
+
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+import android.test.suitebuilder.annotation.Suppress;
+import dalvik.system.VMRuntime;
+import junit.framework.TestCase;
+
+import java.lang.ref.PhantomReference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
+import java.lang.ref.WeakReference;
+import java.util.LinkedList;
+import java.util.Random;
+
+
+public class HeapTest extends TestCase {
+
+ private static final String TAG = "HeapTest";
+
+ /**
+ * Returns a WeakReference to an object that has no
+ * other references. This is done in a separate method
+ * to ensure that the Object's address isn't sitting in
+ * a stale local register.
+ */
+ private WeakReference<Object> newRef() {
+ return new WeakReference<Object>(new Object());
+ }
+
+ /**
+ * Allocates the specified number of bytes. This is done in a separate method
+ * to ensure that the Object's address isn't sitting in a stale local register.
+ */
+ private void allocateMemory(int size) {
+ byte[] b = new byte[size];
+ }
+
+ @MediumTest
+ public void testMinimumHeapSize() throws Exception {
+ VMRuntime r = VMRuntime.getRuntime();
+ final boolean RUN_FLAKY = false;
+
+ long origSize = r.getMinimumHeapSize();
+ if (RUN_FLAKY) {
+ /* Check that the default value is zero. This will break if anyone
+ * in this process sets the minimum heap size to a positive value
+ * before calling this test.
+ */
+ assertTrue(origSize == 0);
+ }
+
+ long size = 4 * 1024 * 1024;
+ long oldSize = r.setMinimumHeapSize(size);
+ assertTrue(oldSize == origSize);
+
+ long newSize = r.getMinimumHeapSize();
+ /* This will fail if the maximum heap size (-Xmx) is smaller than 4MB.
+ */
+ assertTrue(newSize == size);
+
+ /* Make sure that getting the size doesn't change anything.
+ */
+ newSize = r.getMinimumHeapSize();
+ assertTrue(newSize == size);
+
+ /* This test is flaky; if the heap is already large and fragmented,
+ * it can fail. It can also fail if another thread causes a GC
+ * at the wrong time.
+ */
+ if (RUN_FLAKY) {
+ /* Increase the minimum size, allocate a big object, and make sure that
+ * a GC didn't happen.
+ */
+ WeakReference ref = newRef();
+ assertNotNull(ref.get());
+
+ r.setMinimumHeapSize(8 * 1024 * 1024);
+ allocateMemory(4 * 1024 * 1024);
+
+ /* If a GC happened, this reference will be null.
+ */
+ assertNotNull(ref.get());
+ }
+
+ /* Restore the original setting.
+ */
+ r.setMinimumHeapSize(origSize);
+ newSize = r.getMinimumHeapSize();
+ assertTrue(newSize == origSize);
+
+ /* Clean up any large stuff we've allocated,
+ * and re-establish the normal utilization ratio.
+ */
+ Runtime.getRuntime().gc();
+ }
+
+ private static void makeRefs(Object objects[], SoftReference<Object> refs[]) {
+ for (int i = 0; i < objects.length; i++) {
+ objects[i] = (Object) new byte[8 * 1024];
+ refs[i] = new SoftReference<Object>(objects[i]);
+ }
+ }
+
+ private static <T> int checkRefs(SoftReference<T> refs[], int last) {
+ int i;
+ int numCleared = 0;
+ for (i = 0; i < refs.length; i++) {
+ Object o = refs[i].get();
+ if (o == null) {
+ numCleared++;
+ }
+ }
+ if (numCleared != last) {
+ Log.i(TAG, "****** " + numCleared + "/" + i + " cleared ******");
+ }
+ return numCleared;
+ }
+
+ private static void clearRefs(Object objects[], int skip) {
+ for (int i = 0; i < objects.length; i += skip) {
+ objects[i] = null;
+ }
+ }
+
+ private static void clearRefs(Object objects[]) {
+ clearRefs(objects, 1);
+ }
+
+ private static <T> void checkRefs(T objects[], SoftReference<T> refs[]) {
+ boolean ok = true;
+
+ for (int i = 0; i < objects.length; i++) {
+ if (refs[i].get() != objects[i]) {
+ ok = false;
+ }
+ }
+ if (!ok) {
+ throw new RuntimeException("Test failed: soft refs not cleared");
+ }
+ }
+
+ @MediumTest
+ public void testGcSoftRefs() throws Exception {
+ final int NUM_REFS = 128;
+
+ Object objects[] = new Object[NUM_REFS];
+ SoftReference<Object> refs[] = new SoftReference[objects.length];
+
+ /* Create a bunch of objects and a parallel array
+ * of SoftReferences.
+ */
+ makeRefs(objects, refs);
+ Runtime.getRuntime().gc();
+
+ /* Let go of some of the hard references to the objects so that
+ * the references can be cleared.
+ */
+ clearRefs(objects, 3);
+
+ /* Collect all softly-reachable objects.
+ */
+ VMRuntime.getRuntime().gcSoftReferences();
+ Runtime.getRuntime().runFinalization();
+
+ /* Make sure that the objects were collected.
+ */
+ checkRefs(objects, refs);
+
+ /* Remove more hard references and re-check.
+ */
+ clearRefs(objects, 2);
+ VMRuntime.getRuntime().gcSoftReferences();
+ Runtime.getRuntime().runFinalization();
+ checkRefs(objects, refs);
+
+ /* Remove the rest of the references and re-check.
+ */
+ /* Remove more hard references and re-check.
+ */
+ clearRefs(objects);
+ VMRuntime.getRuntime().gcSoftReferences();
+ Runtime.getRuntime().runFinalization();
+ checkRefs(objects, refs);
+ }
+
+ public void xxtestSoftRefPartialClean() throws Exception {
+ final int NUM_REFS = 128;
+
+ Object objects[] = new Object[NUM_REFS];
+ SoftReference<Object> refs[] = new SoftReference[objects.length];
+
+ /* Create a bunch of objects and a parallel array
+ * of SoftReferences.
+ */
+ makeRefs(objects, refs);
+ Runtime.getRuntime().gc();
+
+ /* Let go of the hard references to the objects so that
+ * the references can be cleared.
+ */
+ clearRefs(objects);
+
+ /* Start creating a bunch of temporary and permanent objects
+ * to drive GC.
+ */
+ final int NUM_OBJECTS = 64 * 1024;
+ Object junk[] = new Object[NUM_OBJECTS];
+ Random random = new Random();
+
+ int i = 0;
+ int mod = 0;
+ int totalSize = 0;
+ int cleared = -1;
+ while (i < junk.length && totalSize < 8 * 1024 * 1024) {
+ int r = random.nextInt(64 * 1024) + 128;
+ Object o = (Object) new byte[r];
+ if (++mod % 16 == 0) {
+ junk[i++] = o;
+ totalSize += r * 4;
+ }
+ cleared = checkRefs(refs, cleared);
+ }
+ }
+
+ private static void makeRefs(Object objects[], WeakReference<Object> refs[]) {
+ for (int i = 0; i < objects.length; i++) {
+ objects[i] = new Object();
+ refs[i] = new WeakReference<Object>(objects[i]);
+ }
+ }
+
+ private static <T> void checkRefs(T objects[], WeakReference<T> refs[]) {
+ boolean ok = true;
+
+ for (int i = 0; i < objects.length; i++) {
+ if (refs[i].get() != objects[i]) {
+ ok = false;
+ }
+ }
+ if (!ok) {
+ throw new RuntimeException("Test failed: " +
+ "weak refs not cleared");
+ }
+ }
+
+ @MediumTest
+ public void testWeakRefs() throws Exception {
+ final int NUM_REFS = 16;
+
+ Object objects[] = new Object[NUM_REFS];
+ WeakReference<Object> refs[] = new WeakReference[objects.length];
+
+ /* Create a bunch of objects and a parallel array
+ * of WeakReferences.
+ */
+ makeRefs(objects, refs);
+ Runtime.getRuntime().gc();
+ checkRefs(objects, refs);
+
+ /* Clear out every other strong reference.
+ */
+ for (int i = 0; i < objects.length; i += 2) {
+ objects[i] = null;
+ }
+ Runtime.getRuntime().gc();
+ checkRefs(objects, refs);
+
+ /* Clear out the rest of them.
+ */
+ for (int i = 0; i < objects.length; i++) {
+ objects[i] = null;
+ }
+ Runtime.getRuntime().gc();
+ checkRefs(objects, refs);
+ }
+
+ private static void makeRefs(Object objects[], PhantomReference<Object> refs[],
+ ReferenceQueue<Object> queue) {
+ for (int i = 0; i < objects.length; i++) {
+ objects[i] = new Object();
+ refs[i] = new PhantomReference<Object>(objects[i], queue);
+ }
+ }
+
+ static <T> void checkRefs(T objects[], PhantomReference<T> refs[],
+ ReferenceQueue<T> queue) {
+ boolean ok = true;
+
+ /* Make sure that the reference that should be on
+ * the queue are marked as enqueued. Once we
+ * pull them off the queue, they will no longer
+ * be marked as enqueued.
+ */
+ for (int i = 0; i < objects.length; i++) {
+ if (objects[i] == null && refs[i] != null) {
+ if (!refs[i].isEnqueued()) {
+ ok = false;
+ }
+ }
+ }
+ if (!ok) {
+ throw new RuntimeException("Test failed: " +
+ "phantom refs not marked as enqueued");
+ }
+
+ /* Make sure that all of the references on the queue
+ * are supposed to be there.
+ */
+ PhantomReference<T> ref;
+ while ((ref = (PhantomReference<T>) queue.poll()) != null) {
+ /* Find the list index that corresponds to this reference.
+ */
+ int i;
+ for (i = 0; i < objects.length; i++) {
+ if (refs[i] == ref) {
+ break;
+ }
+ }
+ if (i == objects.length) {
+ throw new RuntimeException("Test failed: " +
+ "unexpected ref on queue");
+ }
+ if (objects[i] != null) {
+ throw new RuntimeException("Test failed: " +
+ "reference enqueued for strongly-reachable " +
+ "object");
+ }
+ refs[i] = null;
+
+ /* TODO: clear doesn't do much, since we're losing the
+ * strong ref to the ref object anyway. move the ref
+ * into another list.
+ */
+ ref.clear();
+ }
+
+ /* We've visited all of the enqueued references.
+ * Make sure that there aren't any other references
+ * that should have been enqueued.
+ *
+ * NOTE: there is a race condition here; this assumes
+ * that the VM has serviced all outstanding reference
+ * enqueue() calls.
+ */
+ for (int i = 0; i < objects.length; i++) {
+ if (objects[i] == null && refs[i] != null) {
+// System.out.println("HeapTest/PhantomRefs: refs[" + i +
+// "] should be enqueued");
+ ok = false;
+ }
+ }
+ if (!ok) {
+ throw new RuntimeException("Test failed: " +
+ "phantom refs not enqueued");
+ }
+ }
+
+ @MediumTest
+ public void testPhantomRefs() throws Exception {
+ final int NUM_REFS = 16;
+
+ Object objects[] = new Object[NUM_REFS];
+ PhantomReference<Object> refs[] = new PhantomReference[objects.length];
+ ReferenceQueue<Object> queue = new ReferenceQueue<Object>();
+
+ /* Create a bunch of objects and a parallel array
+ * of PhantomReferences.
+ */
+ makeRefs(objects, refs, queue);
+ Runtime.getRuntime().gc();
+ checkRefs(objects, refs, queue);
+
+ /* Clear out every other strong reference.
+ */
+ for (int i = 0; i < objects.length; i += 2) {
+ objects[i] = null;
+ }
+ // System.out.println("HeapTest/PhantomRefs: cleared evens");
+ Runtime.getRuntime().gc();
+ Runtime.getRuntime().runFinalization();
+ checkRefs(objects, refs, queue);
+
+ /* Clear out the rest of them.
+ */
+ for (int i = 0; i < objects.length; i++) {
+ objects[i] = null;
+ }
+ // System.out.println("HeapTest/PhantomRefs: cleared all");
+ Runtime.getRuntime().gc();
+ Runtime.getRuntime().runFinalization();
+ checkRefs(objects, refs, queue);
+ }
+
+ private static int sNumFinalized = 0;
+ private static final Object sLock = new Object();
+
+ private static class FinalizableObject {
+ protected void finalize() {
+ // System.out.println("gc from finalize()");
+ Runtime.getRuntime().gc();
+ synchronized (sLock) {
+ sNumFinalized++;
+ }
+ }
+ }
+
+ private static void makeRefs(FinalizableObject objects[],
+ WeakReference<FinalizableObject> refs[]) {
+ for (int i = 0; i < objects.length; i++) {
+ objects[i] = new FinalizableObject();
+ refs[i] = new WeakReference<FinalizableObject>(objects[i]);
+ }
+ }
+
+ @LargeTest
+ public void testWeakRefsAndFinalizers() throws Exception {
+ final int NUM_REFS = 16;
+
+ FinalizableObject objects[] = new FinalizableObject[NUM_REFS];
+ WeakReference<FinalizableObject> refs[] = new WeakReference[objects.length];
+ int numCleared;
+
+ /* Create a bunch of objects and a parallel array
+ * of WeakReferences.
+ */
+ makeRefs(objects, refs);
+ Runtime.getRuntime().gc();
+ checkRefs(objects, refs);
+
+ /* Clear out every other strong reference.
+ */
+ sNumFinalized = 0;
+ numCleared = 0;
+ for (int i = 0; i < objects.length; i += 2) {
+ objects[i] = null;
+ numCleared++;
+ }
+ // System.out.println("HeapTest/WeakRefsAndFinalizers: cleared evens");
+ Runtime.getRuntime().gc();
+ Runtime.getRuntime().runFinalization();
+ checkRefs(objects, refs);
+ if (sNumFinalized != numCleared) {
+ throw new RuntimeException("Test failed: " +
+ "expected " + numCleared + " finalizations, saw " +
+ sNumFinalized);
+ }
+
+ /* Clear out the rest of them.
+ */
+ sNumFinalized = 0;
+ numCleared = 0;
+ for (int i = 0; i < objects.length; i++) {
+ if (objects[i] != null) {
+ objects[i] = null;
+ numCleared++;
+ }
+ }
+ // System.out.println("HeapTest/WeakRefsAndFinalizers: cleared all");
+ Runtime.getRuntime().gc();
+ Runtime.getRuntime().runFinalization();
+ checkRefs(objects, refs);
+ if (sNumFinalized != numCleared) {
+ throw new RuntimeException("Test failed: " +
+ "expected " + numCleared + " finalizations, saw " +
+ sNumFinalized);
+ }
+ }
+
+ @MediumTest
+ public void testOomeLarge() throws Exception {
+ /* Just shy of the typical max heap size so that it will actually
+ * try to allocate it instead of short-circuiting.
+ */
+ final int SIXTEEN_MB = (16 * 1024 * 1024 - 32);
+
+ Boolean sawEx = false;
+ byte a[];
+
+ try {
+ a = new byte[SIXTEEN_MB];
+ } catch (OutOfMemoryError oom) {
+ //Log.i(TAG, "HeapTest/OomeLarge caught " + oom);
+ sawEx = true;
+ }
+
+ if (!sawEx) {
+ throw new RuntimeException("Test failed: " +
+ "OutOfMemoryError not thrown");
+ }
+ }
+
+ //See bug 1308253 for reasons.
+ @Suppress
+ public void disableTestOomeSmall() throws Exception {
+ final int SIXTEEN_MB = (16 * 1024 * 1024);
+ final int LINK_SIZE = 6 * 4; // estimated size of a LinkedList's node
+
+ Boolean sawEx = false;
+
+ LinkedList<Object> list = new LinkedList<Object>();
+
+ /* Allocate progressively smaller objects to fill up the entire heap.
+ */
+ int objSize = 1 * 1024 * 1024;
+ while (objSize >= LINK_SIZE) {
+ try {
+ for (int i = 0; i < SIXTEEN_MB / objSize; i++) {
+ list.add((Object)new byte[objSize]);
+ }
+ } catch (OutOfMemoryError oom) {
+ sawEx = true;
+ }
+
+ if (!sawEx) {
+ throw new RuntimeException("Test failed: " +
+ "OutOfMemoryError not thrown while filling heap");
+ }
+ sawEx = false;
+
+ objSize = (objSize * 4) / 5;
+ }
+ }
+
+ @SmallTest
+ public void testExternalOomeLarge() {
+ /* Just shy of the typical max heap size so that it will actually
+ * try to allocate it instead of short-circuiting.
+ */
+ final int HUGE_SIZE = (16 * 1024 * 1024 - 32);
+
+ assertFalse(VMRuntime.getRuntime().trackExternalAllocation(HUGE_SIZE));
+ }
+
+ /**
+ * "Allocates" external memory in progressively smaller chunks until there's
+ * only roughly 16 bytes left.
+ *
+ * @return the number of bytes allocated
+ */
+ private long allocateMaxExternal() {
+ final VMRuntime runtime = VMRuntime.getRuntime();
+ final int SIXTEEN_MB = (16 * 1024 * 1024);
+ final int MIN_SIZE = 16;
+ long totalAllocated = 0;
+ boolean success;
+
+ success = false;
+ try {
+ /* "Allocate" progressively smaller chunks to "fill up" the entire heap.
+ */
+ int objSize = 1 * 1024 * 1024;
+ while (objSize >= MIN_SIZE) {
+ boolean sawFailure = false;
+ for (int i = 0; i < SIXTEEN_MB / objSize; i++) {
+ if (runtime.trackExternalAllocation(objSize)) {
+ totalAllocated += objSize;
+ } else {
+ sawFailure = true;
+ break;
+ }
+ }
+
+ if (!sawFailure) {
+ throw new RuntimeException("Test failed: " +
+ "no failure while filling heap");
+ }
+
+ objSize = (objSize * 4) / 5;
+ }
+ success = true;
+ } finally {
+ if (!success) {
+ runtime.trackExternalFree(totalAllocated);
+ totalAllocated = 0;
+ }
+ }
+ return totalAllocated;
+ }
+
+ public void xxtest00ExternalOomeSmall() {
+ VMRuntime.getRuntime().trackExternalFree(allocateMaxExternal());
+ }
+
+ /**
+ * Allocates as much external memory as possible, then allocates from the heap
+ * until an OOME is caught.
+ *
+ * It's nice to run this test while the real heap is small, hence the '00' in its
+ * name to force it to run before testOomeSmall().
+ */
+ public void xxtest00CombinedOomeSmall() {
+ long totalAllocated = 0;
+ boolean sawEx = false;
+ try {
+ totalAllocated = allocateMaxExternal();
+ LinkedList<Object> list = new LinkedList<Object>();
+ try {
+ while (true) {
+ list.add((Object)new byte[8192]);
+ }
+ /*NOTREACHED*/
+ } catch (OutOfMemoryError oom) {
+ sawEx = true;
+ }
+ } finally {
+ VMRuntime.getRuntime().trackExternalFree(totalAllocated);
+ }
+ assertTrue(sawEx);
+ }
+
+ //TODO: test external alloc debugging/inspection
+}