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 android.core;
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    private static void makeRefs(Object objects[], SoftReference<Object> refs[]) {
50        for (int i = 0; i < objects.length; i++) {
51            objects[i] = (Object) new byte[8 * 1024];
52            refs[i] = new SoftReference<Object>(objects[i]);
53        }
54    }
55
56    private static <T> int checkRefs(SoftReference<T> refs[], int last) {
57        int i;
58        int numCleared = 0;
59        for (i = 0; i < refs.length; i++) {
60            Object o = refs[i].get();
61            if (o == null) {
62                numCleared++;
63            }
64        }
65        if (numCleared != last) {
66            Log.i(TAG, "****** " + numCleared + "/" + i + " cleared ******");
67        }
68        return numCleared;
69    }
70
71    private static void clearRefs(Object objects[], int skip) {
72        for (int i = 0; i < objects.length; i += skip) {
73            objects[i] = null;
74        }
75    }
76
77    private static void clearRefs(Object objects[]) {
78        clearRefs(objects, 1);
79    }
80
81    private static <T> void checkRefs(T objects[], SoftReference<T> refs[]) {
82        boolean ok = true;
83
84        for (int i = 0; i < objects.length; i++) {
85            if (refs[i].get() != objects[i]) {
86                ok = false;
87            }
88        }
89        if (!ok) {
90            throw new RuntimeException("Test failed: soft refs not cleared");
91        }
92    }
93
94    @MediumTest
95    public void testGcSoftRefs() throws Exception {
96        final int NUM_REFS = 128;
97
98        Object objects[] = new Object[NUM_REFS];
99        SoftReference<Object> refs[] = new SoftReference[objects.length];
100
101        /* Create a bunch of objects and a parallel array
102         * of SoftReferences.
103         */
104        makeRefs(objects, refs);
105        Runtime.getRuntime().gc();
106
107        /* Let go of some of the hard references to the objects so that
108         * the references can be cleared.
109         */
110        clearRefs(objects, 3);
111
112        /* Collect all softly-reachable objects.
113         */
114        VMRuntime.getRuntime().gcSoftReferences();
115        Runtime.getRuntime().runFinalization();
116
117        /* Make sure that the objects were collected.
118         */
119        checkRefs(objects, refs);
120
121        /* Remove more hard references and re-check.
122         */
123        clearRefs(objects, 2);
124        VMRuntime.getRuntime().gcSoftReferences();
125        Runtime.getRuntime().runFinalization();
126        checkRefs(objects, refs);
127
128        /* Remove the rest of the references and re-check.
129         */
130        /* Remove more hard references and re-check.
131         */
132        clearRefs(objects);
133        VMRuntime.getRuntime().gcSoftReferences();
134        Runtime.getRuntime().runFinalization();
135        checkRefs(objects, refs);
136    }
137
138    public void xxtestSoftRefPartialClean() throws Exception {
139        final int NUM_REFS = 128;
140
141        Object objects[] = new Object[NUM_REFS];
142        SoftReference<Object> refs[] = new SoftReference[objects.length];
143
144        /* Create a bunch of objects and a parallel array
145        * of SoftReferences.
146        */
147        makeRefs(objects, refs);
148        Runtime.getRuntime().gc();
149
150        /* Let go of the hard references to the objects so that
151        * the references can be cleared.
152        */
153        clearRefs(objects);
154
155        /* Start creating a bunch of temporary and permanent objects
156        * to drive GC.
157        */
158        final int NUM_OBJECTS = 64 * 1024;
159        Object junk[] = new Object[NUM_OBJECTS];
160        Random random = new Random();
161
162        int i = 0;
163        int mod = 0;
164        int totalSize = 0;
165        int cleared = -1;
166        while (i < junk.length && totalSize < 8 * 1024 * 1024) {
167            int r = random.nextInt(64 * 1024) + 128;
168            Object o = (Object) new byte[r];
169            if (++mod % 16 == 0) {
170                junk[i++] = o;
171                totalSize += r * 4;
172            }
173            cleared = checkRefs(refs, cleared);
174        }
175    }
176
177    private static void makeRefs(Object objects[], WeakReference<Object> refs[]) {
178        for (int i = 0; i < objects.length; i++) {
179            objects[i] = new Object();
180            refs[i] = new WeakReference<Object>(objects[i]);
181        }
182    }
183
184    private static <T> void checkRefs(T objects[], WeakReference<T> refs[]) {
185        boolean ok = true;
186
187        for (int i = 0; i < objects.length; i++) {
188            if (refs[i].get() != objects[i]) {
189                ok = false;
190            }
191        }
192        if (!ok) {
193            throw new RuntimeException("Test failed: " +
194                    "weak refs not cleared");
195        }
196    }
197
198    @MediumTest
199    public void testWeakRefs() throws Exception {
200        final int NUM_REFS = 16;
201
202        Object objects[] = new Object[NUM_REFS];
203        WeakReference<Object> refs[] = new WeakReference[objects.length];
204
205        /* Create a bunch of objects and a parallel array
206        * of WeakReferences.
207        */
208        makeRefs(objects, refs);
209        Runtime.getRuntime().gc();
210        checkRefs(objects, refs);
211
212        /* Clear out every other strong reference.
213        */
214        for (int i = 0; i < objects.length; i += 2) {
215            objects[i] = null;
216        }
217        Runtime.getRuntime().gc();
218        checkRefs(objects, refs);
219
220        /* Clear out the rest of them.
221        */
222        for (int i = 0; i < objects.length; i++) {
223            objects[i] = null;
224        }
225        Runtime.getRuntime().gc();
226        checkRefs(objects, refs);
227    }
228
229    private static void makeRefs(Object objects[], PhantomReference<Object> refs[],
230            ReferenceQueue<Object> queue) {
231        for (int i = 0; i < objects.length; i++) {
232            objects[i] = new Object();
233            refs[i] = new PhantomReference<Object>(objects[i], queue);
234        }
235    }
236
237    static <T> void checkRefs(T objects[], PhantomReference<T> refs[],
238            ReferenceQueue<T> queue) {
239        boolean ok = true;
240
241        /* Make sure that the reference that should be on
242        * the queue are marked as enqueued.  Once we
243        * pull them off the queue, they will no longer
244        * be marked as enqueued.
245        */
246        for (int i = 0; i < objects.length; i++) {
247            if (objects[i] == null && refs[i] != null) {
248                if (!refs[i].isEnqueued()) {
249                    ok = false;
250                }
251            }
252        }
253        if (!ok) {
254            throw new RuntimeException("Test failed: " +
255                    "phantom refs not marked as enqueued");
256        }
257
258        /* Make sure that all of the references on the queue
259        * are supposed to be there.
260        */
261        PhantomReference<T> ref;
262        while ((ref = (PhantomReference<T>) queue.poll()) != null) {
263            /* Find the list index that corresponds to this reference.
264            */
265            int i;
266            for (i = 0; i < objects.length; i++) {
267                if (refs[i] == ref) {
268                    break;
269                }
270            }
271            if (i == objects.length) {
272                throw new RuntimeException("Test failed: " +
273                        "unexpected ref on queue");
274            }
275            if (objects[i] != null) {
276                throw new RuntimeException("Test failed: " +
277                        "reference enqueued for strongly-reachable " +
278                        "object");
279            }
280            refs[i] = null;
281
282            /* TODO: clear doesn't do much, since we're losing the
283            * strong ref to the ref object anyway.  move the ref
284            * into another list.
285            */
286            ref.clear();
287        }
288
289        /* We've visited all of the enqueued references.
290        * Make sure that there aren't any other references
291        * that should have been enqueued.
292        *
293        * NOTE: there is a race condition here;  this assumes
294        * that the VM has serviced all outstanding reference
295        * enqueue() calls.
296        */
297        for (int i = 0; i < objects.length; i++) {
298            if (objects[i] == null && refs[i] != null) {
299//                System.out.println("HeapTest/PhantomRefs: refs[" + i +
300//                        "] should be enqueued");
301                ok = false;
302            }
303        }
304        if (!ok) {
305            throw new RuntimeException("Test failed: " +
306                    "phantom refs not enqueued");
307        }
308    }
309
310    @MediumTest
311    public void testPhantomRefs() throws Exception {
312        final int NUM_REFS = 16;
313
314        Object objects[] = new Object[NUM_REFS];
315        PhantomReference<Object> refs[] = new PhantomReference[objects.length];
316        ReferenceQueue<Object> queue = new ReferenceQueue<Object>();
317
318        /* Create a bunch of objects and a parallel array
319        * of PhantomReferences.
320        */
321        makeRefs(objects, refs, queue);
322        Runtime.getRuntime().gc();
323        checkRefs(objects, refs, queue);
324
325        /* Clear out every other strong reference.
326        */
327        for (int i = 0; i < objects.length; i += 2) {
328            objects[i] = null;
329        }
330        // System.out.println("HeapTest/PhantomRefs: cleared evens");
331        Runtime.getRuntime().gc();
332        Runtime.getRuntime().runFinalization();
333        checkRefs(objects, refs, queue);
334
335        /* Clear out the rest of them.
336        */
337        for (int i = 0; i < objects.length; i++) {
338            objects[i] = null;
339        }
340        // System.out.println("HeapTest/PhantomRefs: cleared all");
341        Runtime.getRuntime().gc();
342        Runtime.getRuntime().runFinalization();
343        checkRefs(objects, refs, queue);
344    }
345
346    private static int sNumFinalized = 0;
347    private static final Object sLock = new Object();
348
349    private static class FinalizableObject {
350        protected void finalize() {
351            // System.out.println("gc from finalize()");
352            Runtime.getRuntime().gc();
353            synchronized (sLock) {
354                sNumFinalized++;
355            }
356        }
357    }
358
359    private static void makeRefs(FinalizableObject objects[],
360            WeakReference<FinalizableObject> refs[]) {
361        for (int i = 0; i < objects.length; i++) {
362            objects[i] = new FinalizableObject();
363            refs[i] = new WeakReference<FinalizableObject>(objects[i]);
364        }
365    }
366
367    @LargeTest
368    public void testWeakRefsAndFinalizers() throws Exception {
369        final int NUM_REFS = 16;
370
371        FinalizableObject objects[] = new FinalizableObject[NUM_REFS];
372        WeakReference<FinalizableObject> refs[] = new WeakReference[objects.length];
373        int numCleared;
374
375        /* Create a bunch of objects and a parallel array
376        * of WeakReferences.
377        */
378        makeRefs(objects, refs);
379        Runtime.getRuntime().gc();
380        checkRefs(objects, refs);
381
382        /* Clear out every other strong reference.
383        */
384        sNumFinalized = 0;
385        numCleared = 0;
386        for (int i = 0; i < objects.length; i += 2) {
387            objects[i] = null;
388            numCleared++;
389        }
390        // System.out.println("HeapTest/WeakRefsAndFinalizers: cleared evens");
391        Runtime.getRuntime().gc();
392        Runtime.getRuntime().runFinalization();
393        checkRefs(objects, refs);
394        if (sNumFinalized != numCleared) {
395            throw new RuntimeException("Test failed: " +
396                    "expected " + numCleared + " finalizations, saw " +
397                    sNumFinalized);
398        }
399
400        /* Clear out the rest of them.
401        */
402        sNumFinalized = 0;
403        numCleared = 0;
404        for (int i = 0; i < objects.length; i++) {
405            if (objects[i] != null) {
406                objects[i] = null;
407                numCleared++;
408            }
409        }
410        // System.out.println("HeapTest/WeakRefsAndFinalizers: cleared all");
411        Runtime.getRuntime().gc();
412        Runtime.getRuntime().runFinalization();
413        checkRefs(objects, refs);
414        if (sNumFinalized != numCleared) {
415            throw new RuntimeException("Test failed: " +
416                    "expected " + numCleared + " finalizations, saw " +
417                    sNumFinalized);
418        }
419    }
420
421    // TODO: flaky test
422    //@MediumTest
423    public void testOomeLarge() throws Exception {
424        /* Just shy of the typical max heap size so that it will actually
425         * try to allocate it instead of short-circuiting.
426         */
427        final int SIXTEEN_MB = (16 * 1024 * 1024 - 32);
428
429        Boolean sawEx = false;
430        byte a[];
431
432        try {
433            a = new byte[SIXTEEN_MB];
434        } catch (OutOfMemoryError oom) {
435            //Log.i(TAG, "HeapTest/OomeLarge caught " + oom);
436            sawEx = true;
437        }
438
439        if (!sawEx) {
440            throw new RuntimeException("Test failed: " +
441                    "OutOfMemoryError not thrown");
442        }
443    }
444
445    //See bug 1308253 for reasons.
446    @Suppress
447    public void disableTestOomeSmall() throws Exception {
448        final int SIXTEEN_MB = (16 * 1024 * 1024);
449        final int LINK_SIZE = 6 * 4; // estimated size of a LinkedList's node
450
451        Boolean sawEx = false;
452
453        LinkedList<Object> list = new LinkedList<Object>();
454
455        /* Allocate progressively smaller objects to fill up the entire heap.
456         */
457        int objSize = 1 * 1024 * 1024;
458        while (objSize >= LINK_SIZE) {
459            try {
460                for (int i = 0; i < SIXTEEN_MB / objSize; i++) {
461                    list.add((Object)new byte[objSize]);
462                }
463            } catch (OutOfMemoryError oom) {
464                sawEx = true;
465            }
466
467            if (!sawEx) {
468                throw new RuntimeException("Test failed: " +
469                        "OutOfMemoryError not thrown while filling heap");
470            }
471            sawEx = false;
472
473            objSize = (objSize * 4) / 5;
474        }
475    }
476}
477