diff options
Diffstat (limited to 'tests/AndroidTests/src/com/android/unit_tests/HeapTest.java')
-rw-r--r-- | tests/AndroidTests/src/com/android/unit_tests/HeapTest.java | 631 |
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 +} |