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