Home | History | Annotate | Download | only in core
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.core;
     18 
     19 import android.test.suitebuilder.annotation.LargeTest;
     20 import android.test.suitebuilder.annotation.MediumTest;
     21 import android.test.suitebuilder.annotation.SmallTest;
     22 import android.util.Log;
     23 import android.test.suitebuilder.annotation.Suppress;
     24 import dalvik.system.VMRuntime;
     25 import junit.framework.TestCase;
     26 
     27 import java.lang.ref.PhantomReference;
     28 import java.lang.ref.ReferenceQueue;
     29 import java.lang.ref.SoftReference;
     30 import java.lang.ref.WeakReference;
     31 import java.util.LinkedList;
     32 import java.util.Random;
     33 
     34 
     35 public class HeapTest extends TestCase {
     36 
     37     private static final String TAG = "HeapTest";
     38 
     39     /**
     40      * Returns a WeakReference to an object that has no
     41      * other references.  This is done in a separate method
     42      * to ensure that the Object's address isn't sitting in
     43      * a stale local register.
     44      */
     45     private WeakReference<Object> newRef() {
     46         return new WeakReference<Object>(new Object());
     47     }
     48 
     49     /**
     50      * Allocates the specified number of bytes. This is done in a separate method
     51      * to ensure that the Object's address isn't sitting in a stale local register.
     52      */
     53     private void allocateMemory(int size) {
     54         byte[] b = new byte[size];
     55     }
     56 
     57     @MediumTest
     58     public void testMinimumHeapSize() throws Exception {
     59         VMRuntime r = VMRuntime.getRuntime();
     60         final boolean RUN_FLAKY = false;
     61 
     62         long origSize = r.getMinimumHeapSize();
     63         if (RUN_FLAKY) {
     64             /* Check that the default value is zero.  This will break if anyone
     65              * in this process sets the minimum heap size to a positive value
     66              * before calling this test.
     67              */
     68             assertTrue(origSize == 0);
     69         }
     70 
     71         long size = 4 * 1024 * 1024;
     72         long oldSize = r.setMinimumHeapSize(size);
     73         assertTrue(oldSize == origSize);
     74 
     75         long newSize = r.getMinimumHeapSize();
     76         /* This will fail if the maximum heap size (-Xmx) is smaller than 4MB.
     77          */
     78         assertTrue(newSize == size);
     79 
     80         /* Make sure that getting the size doesn't change anything.
     81          */
     82         newSize = r.getMinimumHeapSize();
     83         assertTrue(newSize == size);
     84 
     85         /* This test is flaky; if the heap is already large and fragmented,
     86          * it can fail.  It can also fail if another thread causes a GC
     87          * at the wrong time.
     88          */
     89         if (RUN_FLAKY) {
     90             /* Increase the minimum size, allocate a big object, and make sure that
     91              * a GC didn't happen.
     92              */
     93             WeakReference ref = newRef();
     94             assertNotNull(ref.get());
     95 
     96             r.setMinimumHeapSize(8 * 1024 * 1024);
     97             allocateMemory(4 * 1024 * 1024);
     98 
     99             /* If a GC happened, this reference will be null.
    100              */
    101             assertNotNull(ref.get());
    102         }
    103 
    104         /* Restore the original setting.
    105          */
    106         r.setMinimumHeapSize(origSize);
    107         newSize = r.getMinimumHeapSize();
    108         assertTrue(newSize == origSize);
    109 
    110         /* Clean up any large stuff we've allocated,
    111          * and re-establish the normal utilization ratio.
    112          */
    113         Runtime.getRuntime().gc();
    114     }
    115 
    116     private static void makeRefs(Object objects[], SoftReference<Object> refs[]) {
    117         for (int i = 0; i < objects.length; i++) {
    118             objects[i] = (Object) new byte[8 * 1024];
    119             refs[i] = new SoftReference<Object>(objects[i]);
    120         }
    121     }
    122 
    123     private static <T> int checkRefs(SoftReference<T> refs[], int last) {
    124         int i;
    125         int numCleared = 0;
    126         for (i = 0; i < refs.length; i++) {
    127             Object o = refs[i].get();
    128             if (o == null) {
    129                 numCleared++;
    130             }
    131         }
    132         if (numCleared != last) {
    133             Log.i(TAG, "****** " + numCleared + "/" + i + " cleared ******");
    134         }
    135         return numCleared;
    136     }
    137 
    138     private static void clearRefs(Object objects[], int skip) {
    139         for (int i = 0; i < objects.length; i += skip) {
    140             objects[i] = null;
    141         }
    142     }
    143 
    144     private static void clearRefs(Object objects[]) {
    145         clearRefs(objects, 1);
    146     }
    147 
    148     private static <T> void checkRefs(T objects[], SoftReference<T> refs[]) {
    149         boolean ok = true;
    150 
    151         for (int i = 0; i < objects.length; i++) {
    152             if (refs[i].get() != objects[i]) {
    153                 ok = false;
    154             }
    155         }
    156         if (!ok) {
    157             throw new RuntimeException("Test failed: soft refs not cleared");
    158         }
    159     }
    160 
    161     @MediumTest
    162     public void testGcSoftRefs() throws Exception {
    163         final int NUM_REFS = 128;
    164 
    165         Object objects[] = new Object[NUM_REFS];
    166         SoftReference<Object> refs[] = new SoftReference[objects.length];
    167 
    168         /* Create a bunch of objects and a parallel array
    169          * of SoftReferences.
    170          */
    171         makeRefs(objects, refs);
    172         Runtime.getRuntime().gc();
    173 
    174         /* Let go of some of the hard references to the objects so that
    175          * the references can be cleared.
    176          */
    177         clearRefs(objects, 3);
    178 
    179         /* Collect all softly-reachable objects.
    180          */
    181         VMRuntime.getRuntime().gcSoftReferences();
    182         Runtime.getRuntime().runFinalization();
    183 
    184         /* Make sure that the objects were collected.
    185          */
    186         checkRefs(objects, refs);
    187 
    188         /* Remove more hard references and re-check.
    189          */
    190         clearRefs(objects, 2);
    191         VMRuntime.getRuntime().gcSoftReferences();
    192         Runtime.getRuntime().runFinalization();
    193         checkRefs(objects, refs);
    194 
    195         /* Remove the rest of the references and re-check.
    196          */
    197         /* Remove more hard references and re-check.
    198          */
    199         clearRefs(objects);
    200         VMRuntime.getRuntime().gcSoftReferences();
    201         Runtime.getRuntime().runFinalization();
    202         checkRefs(objects, refs);
    203     }
    204 
    205     public void xxtestSoftRefPartialClean() throws Exception {
    206         final int NUM_REFS = 128;
    207 
    208         Object objects[] = new Object[NUM_REFS];
    209         SoftReference<Object> refs[] = new SoftReference[objects.length];
    210 
    211         /* Create a bunch of objects and a parallel array
    212         * of SoftReferences.
    213         */
    214         makeRefs(objects, refs);
    215         Runtime.getRuntime().gc();
    216 
    217         /* Let go of the hard references to the objects so that
    218         * the references can be cleared.
    219         */
    220         clearRefs(objects);
    221 
    222         /* Start creating a bunch of temporary and permanent objects
    223         * to drive GC.
    224         */
    225         final int NUM_OBJECTS = 64 * 1024;
    226         Object junk[] = new Object[NUM_OBJECTS];
    227         Random random = new Random();
    228 
    229         int i = 0;
    230         int mod = 0;
    231         int totalSize = 0;
    232         int cleared = -1;
    233         while (i < junk.length && totalSize < 8 * 1024 * 1024) {
    234             int r = random.nextInt(64 * 1024) + 128;
    235             Object o = (Object) new byte[r];
    236             if (++mod % 16 == 0) {
    237                 junk[i++] = o;
    238                 totalSize += r * 4;
    239             }
    240             cleared = checkRefs(refs, cleared);
    241         }
    242     }
    243 
    244     private static void makeRefs(Object objects[], WeakReference<Object> refs[]) {
    245         for (int i = 0; i < objects.length; i++) {
    246             objects[i] = new Object();
    247             refs[i] = new WeakReference<Object>(objects[i]);
    248         }
    249     }
    250 
    251     private static <T> void checkRefs(T objects[], WeakReference<T> refs[]) {
    252         boolean ok = true;
    253 
    254         for (int i = 0; i < objects.length; i++) {
    255             if (refs[i].get() != objects[i]) {
    256                 ok = false;
    257             }
    258         }
    259         if (!ok) {
    260             throw new RuntimeException("Test failed: " +
    261                     "weak refs not cleared");
    262         }
    263     }
    264 
    265     @MediumTest
    266     public void testWeakRefs() throws Exception {
    267         final int NUM_REFS = 16;
    268 
    269         Object objects[] = new Object[NUM_REFS];
    270         WeakReference<Object> refs[] = new WeakReference[objects.length];
    271 
    272         /* Create a bunch of objects and a parallel array
    273         * of WeakReferences.
    274         */
    275         makeRefs(objects, refs);
    276         Runtime.getRuntime().gc();
    277         checkRefs(objects, refs);
    278 
    279         /* Clear out every other strong reference.
    280         */
    281         for (int i = 0; i < objects.length; i += 2) {
    282             objects[i] = null;
    283         }
    284         Runtime.getRuntime().gc();
    285         checkRefs(objects, refs);
    286 
    287         /* Clear out the rest of them.
    288         */
    289         for (int i = 0; i < objects.length; i++) {
    290             objects[i] = null;
    291         }
    292         Runtime.getRuntime().gc();
    293         checkRefs(objects, refs);
    294     }
    295 
    296     private static void makeRefs(Object objects[], PhantomReference<Object> refs[],
    297             ReferenceQueue<Object> queue) {
    298         for (int i = 0; i < objects.length; i++) {
    299             objects[i] = new Object();
    300             refs[i] = new PhantomReference<Object>(objects[i], queue);
    301         }
    302     }
    303 
    304     static <T> void checkRefs(T objects[], PhantomReference<T> refs[],
    305             ReferenceQueue<T> queue) {
    306         boolean ok = true;
    307 
    308         /* Make sure that the reference that should be on
    309         * the queue are marked as enqueued.  Once we
    310         * pull them off the queue, they will no longer
    311         * be marked as enqueued.
    312         */
    313         for (int i = 0; i < objects.length; i++) {
    314             if (objects[i] == null && refs[i] != null) {
    315                 if (!refs[i].isEnqueued()) {
    316                     ok = false;
    317                 }
    318             }
    319         }
    320         if (!ok) {
    321             throw new RuntimeException("Test failed: " +
    322                     "phantom refs not marked as enqueued");
    323         }
    324 
    325         /* Make sure that all of the references on the queue
    326         * are supposed to be there.
    327         */
    328         PhantomReference<T> ref;
    329         while ((ref = (PhantomReference<T>) queue.poll()) != null) {
    330             /* Find the list index that corresponds to this reference.
    331             */
    332             int i;
    333             for (i = 0; i < objects.length; i++) {
    334                 if (refs[i] == ref) {
    335                     break;
    336                 }
    337             }
    338             if (i == objects.length) {
    339                 throw new RuntimeException("Test failed: " +
    340                         "unexpected ref on queue");
    341             }
    342             if (objects[i] != null) {
    343                 throw new RuntimeException("Test failed: " +
    344                         "reference enqueued for strongly-reachable " +
    345                         "object");
    346             }
    347             refs[i] = null;
    348 
    349             /* TODO: clear doesn't do much, since we're losing the
    350             * strong ref to the ref object anyway.  move the ref
    351             * into another list.
    352             */
    353             ref.clear();
    354         }
    355 
    356         /* We've visited all of the enqueued references.
    357         * Make sure that there aren't any other references
    358         * that should have been enqueued.
    359         *
    360         * NOTE: there is a race condition here;  this assumes
    361         * that the VM has serviced all outstanding reference
    362         * enqueue() calls.
    363         */
    364         for (int i = 0; i < objects.length; i++) {
    365             if (objects[i] == null && refs[i] != null) {
    366 //                System.out.println("HeapTest/PhantomRefs: refs[" + i +
    367 //                        "] should be enqueued");
    368                 ok = false;
    369             }
    370         }
    371         if (!ok) {
    372             throw new RuntimeException("Test failed: " +
    373                     "phantom refs not enqueued");
    374         }
    375     }
    376 
    377     @MediumTest
    378     public void testPhantomRefs() throws Exception {
    379         final int NUM_REFS = 16;
    380 
    381         Object objects[] = new Object[NUM_REFS];
    382         PhantomReference<Object> refs[] = new PhantomReference[objects.length];
    383         ReferenceQueue<Object> queue = new ReferenceQueue<Object>();
    384 
    385         /* Create a bunch of objects and a parallel array
    386         * of PhantomReferences.
    387         */
    388         makeRefs(objects, refs, queue);
    389         Runtime.getRuntime().gc();
    390         checkRefs(objects, refs, queue);
    391 
    392         /* Clear out every other strong reference.
    393         */
    394         for (int i = 0; i < objects.length; i += 2) {
    395             objects[i] = null;
    396         }
    397         // System.out.println("HeapTest/PhantomRefs: cleared evens");
    398         Runtime.getRuntime().gc();
    399         Runtime.getRuntime().runFinalization();
    400         checkRefs(objects, refs, queue);
    401 
    402         /* Clear out the rest of them.
    403         */
    404         for (int i = 0; i < objects.length; i++) {
    405             objects[i] = null;
    406         }
    407         // System.out.println("HeapTest/PhantomRefs: cleared all");
    408         Runtime.getRuntime().gc();
    409         Runtime.getRuntime().runFinalization();
    410         checkRefs(objects, refs, queue);
    411     }
    412 
    413     private static int sNumFinalized = 0;
    414     private static final Object sLock = new Object();
    415 
    416     private static class FinalizableObject {
    417         protected void finalize() {
    418             // System.out.println("gc from finalize()");
    419             Runtime.getRuntime().gc();
    420             synchronized (sLock) {
    421                 sNumFinalized++;
    422             }
    423         }
    424     }
    425 
    426     private static void makeRefs(FinalizableObject objects[],
    427             WeakReference<FinalizableObject> refs[]) {
    428         for (int i = 0; i < objects.length; i++) {
    429             objects[i] = new FinalizableObject();
    430             refs[i] = new WeakReference<FinalizableObject>(objects[i]);
    431         }
    432     }
    433 
    434     @LargeTest
    435     public void testWeakRefsAndFinalizers() throws Exception {
    436         final int NUM_REFS = 16;
    437 
    438         FinalizableObject objects[] = new FinalizableObject[NUM_REFS];
    439         WeakReference<FinalizableObject> refs[] = new WeakReference[objects.length];
    440         int numCleared;
    441 
    442         /* Create a bunch of objects and a parallel array
    443         * of WeakReferences.
    444         */
    445         makeRefs(objects, refs);
    446         Runtime.getRuntime().gc();
    447         checkRefs(objects, refs);
    448 
    449         /* Clear out every other strong reference.
    450         */
    451         sNumFinalized = 0;
    452         numCleared = 0;
    453         for (int i = 0; i < objects.length; i += 2) {
    454             objects[i] = null;
    455             numCleared++;
    456         }
    457         // System.out.println("HeapTest/WeakRefsAndFinalizers: cleared evens");
    458         Runtime.getRuntime().gc();
    459         Runtime.getRuntime().runFinalization();
    460         checkRefs(objects, refs);
    461         if (sNumFinalized != numCleared) {
    462             throw new RuntimeException("Test failed: " +
    463                     "expected " + numCleared + " finalizations, saw " +
    464                     sNumFinalized);
    465         }
    466 
    467         /* Clear out the rest of them.
    468         */
    469         sNumFinalized = 0;
    470         numCleared = 0;
    471         for (int i = 0; i < objects.length; i++) {
    472             if (objects[i] != null) {
    473                 objects[i] = null;
    474                 numCleared++;
    475             }
    476         }
    477         // System.out.println("HeapTest/WeakRefsAndFinalizers: cleared all");
    478         Runtime.getRuntime().gc();
    479         Runtime.getRuntime().runFinalization();
    480         checkRefs(objects, refs);
    481         if (sNumFinalized != numCleared) {
    482             throw new RuntimeException("Test failed: " +
    483                     "expected " + numCleared + " finalizations, saw " +
    484                     sNumFinalized);
    485         }
    486     }
    487 
    488     // TODO: flaky test
    489     //@MediumTest
    490     public void testOomeLarge() throws Exception {
    491         /* Just shy of the typical max heap size so that it will actually
    492          * try to allocate it instead of short-circuiting.
    493          */
    494         final int SIXTEEN_MB = (16 * 1024 * 1024 - 32);
    495 
    496         Boolean sawEx = false;
    497         byte a[];
    498 
    499         try {
    500             a = new byte[SIXTEEN_MB];
    501         } catch (OutOfMemoryError oom) {
    502             //Log.i(TAG, "HeapTest/OomeLarge caught " + oom);
    503             sawEx = true;
    504         }
    505 
    506         if (!sawEx) {
    507             throw new RuntimeException("Test failed: " +
    508                     "OutOfMemoryError not thrown");
    509         }
    510     }
    511 
    512     //See bug 1308253 for reasons.
    513     @Suppress
    514     public void disableTestOomeSmall() throws Exception {
    515         final int SIXTEEN_MB = (16 * 1024 * 1024);
    516         final int LINK_SIZE = 6 * 4; // estimated size of a LinkedList's node
    517 
    518         Boolean sawEx = false;
    519 
    520         LinkedList<Object> list = new LinkedList<Object>();
    521 
    522         /* Allocate progressively smaller objects to fill up the entire heap.
    523          */
    524         int objSize = 1 * 1024 * 1024;
    525         while (objSize >= LINK_SIZE) {
    526             try {
    527                 for (int i = 0; i < SIXTEEN_MB / objSize; i++) {
    528                     list.add((Object)new byte[objSize]);
    529                 }
    530             } catch (OutOfMemoryError oom) {
    531                 sawEx = true;
    532             }
    533 
    534             if (!sawEx) {
    535                 throw new RuntimeException("Test failed: " +
    536                         "OutOfMemoryError not thrown while filling heap");
    537             }
    538             sawEx = false;
    539 
    540             objSize = (objSize * 4) / 5;
    541         }
    542     }
    543 
    544     // TODO: flaky test
    545     //@SmallTest
    546     public void testExternalOomeLarge() {
    547         /* Just shy of the typical max heap size so that it will actually
    548          * try to allocate it instead of short-circuiting.
    549          */
    550         final int HUGE_SIZE = (16 * 1024 * 1024 - 32);
    551 
    552         assertFalse(VMRuntime.getRuntime().trackExternalAllocation(HUGE_SIZE));
    553     }
    554 
    555     /**
    556      * "Allocates" external memory in progressively smaller chunks until there's
    557      * only roughly 16 bytes left.
    558      *
    559      * @return the number of bytes allocated
    560      */
    561     private long allocateMaxExternal() {
    562         final VMRuntime runtime = VMRuntime.getRuntime();
    563         final int SIXTEEN_MB = (16 * 1024 * 1024);
    564         final int MIN_SIZE = 16;
    565         long totalAllocated = 0;
    566         boolean success;
    567 
    568         success = false;
    569         try {
    570             /* "Allocate" progressively smaller chunks to "fill up" the entire heap.
    571              */
    572             int objSize = 1 * 1024 * 1024;
    573             while (objSize >= MIN_SIZE) {
    574                 boolean sawFailure = false;
    575                 for (int i = 0; i < SIXTEEN_MB / objSize; i++) {
    576                     if (runtime.trackExternalAllocation(objSize)) {
    577                         totalAllocated += objSize;
    578                     } else {
    579                         sawFailure = true;
    580                         break;
    581                     }
    582                 }
    583 
    584                 if (!sawFailure) {
    585                     throw new RuntimeException("Test failed: " +
    586                             "no failure while filling heap");
    587                 }
    588 
    589                 objSize = (objSize * 4) / 5;
    590             }
    591             success = true;
    592         } finally {
    593             if (!success) {
    594                 runtime.trackExternalFree(totalAllocated);
    595                 totalAllocated = 0;
    596             }
    597         }
    598         return totalAllocated;
    599     }
    600 
    601     public void xxtest00ExternalOomeSmall() {
    602         VMRuntime.getRuntime().trackExternalFree(allocateMaxExternal());
    603     }
    604 
    605     /**
    606      * Allocates as much external memory as possible, then allocates from the heap
    607      * until an OOME is caught.
    608      *
    609      * It's nice to run this test while the real heap is small, hence the '00' in its
    610      * name to force it to run before testOomeSmall().
    611      */
    612     public void xxtest00CombinedOomeSmall() {
    613         long totalAllocated = 0;
    614         boolean sawEx = false;
    615         try {
    616             totalAllocated = allocateMaxExternal();
    617             LinkedList<Object> list = new LinkedList<Object>();
    618             try {
    619                 while (true) {
    620                     list.add((Object)new byte[8192]);
    621                 }
    622                 /*NOTREACHED*/
    623             } catch (OutOfMemoryError oom) {
    624                 sawEx = true;
    625             }
    626         } finally {
    627             VMRuntime.getRuntime().trackExternalFree(totalAllocated);
    628         }
    629         assertTrue(sawEx);
    630     }
    631 
    632     //TODO: test external alloc debugging/inspection
    633 }
    634