blob: d51cc26ef3fbd0c81b13118900d79aec12813004 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
Brett Chabotb8a4e5f2009-10-06 19:13:31 -0700488 // TODO: flaky test
489 //@MediumTest
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800490 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 @SmallTest
545 public void testExternalOomeLarge() {
546 /* Just shy of the typical max heap size so that it will actually
547 * try to allocate it instead of short-circuiting.
548 */
549 final int HUGE_SIZE = (16 * 1024 * 1024 - 32);
550
551 assertFalse(VMRuntime.getRuntime().trackExternalAllocation(HUGE_SIZE));
552 }
553
554 /**
555 * "Allocates" external memory in progressively smaller chunks until there's
556 * only roughly 16 bytes left.
557 *
558 * @return the number of bytes allocated
559 */
560 private long allocateMaxExternal() {
561 final VMRuntime runtime = VMRuntime.getRuntime();
562 final int SIXTEEN_MB = (16 * 1024 * 1024);
563 final int MIN_SIZE = 16;
564 long totalAllocated = 0;
565 boolean success;
566
567 success = false;
568 try {
569 /* "Allocate" progressively smaller chunks to "fill up" the entire heap.
570 */
571 int objSize = 1 * 1024 * 1024;
572 while (objSize >= MIN_SIZE) {
573 boolean sawFailure = false;
574 for (int i = 0; i < SIXTEEN_MB / objSize; i++) {
575 if (runtime.trackExternalAllocation(objSize)) {
576 totalAllocated += objSize;
577 } else {
578 sawFailure = true;
579 break;
580 }
581 }
582
583 if (!sawFailure) {
584 throw new RuntimeException("Test failed: " +
585 "no failure while filling heap");
586 }
587
588 objSize = (objSize * 4) / 5;
589 }
590 success = true;
591 } finally {
592 if (!success) {
593 runtime.trackExternalFree(totalAllocated);
594 totalAllocated = 0;
595 }
596 }
597 return totalAllocated;
598 }
599
600 public void xxtest00ExternalOomeSmall() {
601 VMRuntime.getRuntime().trackExternalFree(allocateMaxExternal());
602 }
603
604 /**
605 * Allocates as much external memory as possible, then allocates from the heap
606 * until an OOME is caught.
607 *
608 * It's nice to run this test while the real heap is small, hence the '00' in its
609 * name to force it to run before testOomeSmall().
610 */
611 public void xxtest00CombinedOomeSmall() {
612 long totalAllocated = 0;
613 boolean sawEx = false;
614 try {
615 totalAllocated = allocateMaxExternal();
616 LinkedList<Object> list = new LinkedList<Object>();
617 try {
618 while (true) {
619 list.add((Object)new byte[8192]);
620 }
621 /*NOTREACHED*/
622 } catch (OutOfMemoryError oom) {
623 sawEx = true;
624 }
625 } finally {
626 VMRuntime.getRuntime().trackExternalFree(totalAllocated);
627 }
628 assertTrue(sawEx);
629 }
630
631 //TODO: test external alloc debugging/inspection
632}