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
17package com.android.unit_tests;
18
19import android.test.suitebuilder.annotation.LargeTest;
20import android.test.suitebuilder.annotation.MediumTest;
21import android.test.suitebuilder.annotation.SmallTest;
22import android.util.Log;
23import android.test.suitebuilder.annotation.Suppress;
24import dalvik.system.VMRuntime;
25import junit.framework.TestCase;
26
27import java.lang.ref.PhantomReference;
28import java.lang.ref.ReferenceQueue;
29import java.lang.ref.SoftReference;
30import java.lang.ref.WeakReference;
31import java.util.LinkedList;
32import java.util.Random;
33
34
35public 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