/* * 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 android.core; 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 newRef() { return new WeakReference(new Object()); } private static void makeRefs(Object objects[], SoftReference refs[]) { for (int i = 0; i < objects.length; i++) { objects[i] = (Object) new byte[8 * 1024]; refs[i] = new SoftReference(objects[i]); } } private static int checkRefs(SoftReference 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 void checkRefs(T objects[], SoftReference 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 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 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 refs[]) { for (int i = 0; i < objects.length; i++) { objects[i] = new Object(); refs[i] = new WeakReference(objects[i]); } } private static void checkRefs(T objects[], WeakReference 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 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 refs[], ReferenceQueue queue) { for (int i = 0; i < objects.length; i++) { objects[i] = new Object(); refs[i] = new PhantomReference(objects[i], queue); } } static void checkRefs(T objects[], PhantomReference refs[], ReferenceQueue 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 ref; while ((ref = (PhantomReference) 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 refs[] = new PhantomReference[objects.length]; ReferenceQueue queue = new ReferenceQueue(); /* 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 refs[]) { for (int i = 0; i < objects.length; i++) { objects[i] = new FinalizableObject(); refs[i] = new WeakReference(objects[i]); } } @LargeTest public void testWeakRefsAndFinalizers() throws Exception { final int NUM_REFS = 16; FinalizableObject objects[] = new FinalizableObject[NUM_REFS]; WeakReference 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); } } // TODO: flaky test //@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 list = new LinkedList(); /* 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; } } }