1 /* 2 * Copyright (C) 2013 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 import java.lang.Runtime; 18 import java.lang.ref.ReferenceQueue; 19 import java.lang.ref.PhantomReference; 20 import java.util.concurrent.atomic.AtomicInteger; 21 import dalvik.system.VMRuntime; 22 23 public class Main { 24 static Object deadlockLock = new Object(); 25 static VMRuntime runtime = VMRuntime.getRuntime(); 26 static volatile boolean aboutToDeadlock = false; 27 static final long MAX_EXPECTED_GC_DURATION_MS = 2000; 28 29 // Save ref as a static field to ensure it doesn't get GC'd before the 30 // referent is enqueued. 31 static PhantomReference ref = null; 32 33 static class DeadlockingFinalizer { finalize()34 protected void finalize() throws Exception { 35 aboutToDeadlock = true; 36 synchronized (deadlockLock) { } 37 } 38 } 39 $noinline$allocateDeadlockingFinalizer()40 private static void $noinline$allocateDeadlockingFinalizer() { 41 new DeadlockingFinalizer(); 42 } 43 44 static AtomicInteger finalizeCounter = new AtomicInteger(0); 45 46 static class IncrementingFinalizer { finalize()47 protected void finalize() throws Exception { 48 finalizeCounter.incrementAndGet(); 49 } 50 } 51 $noinline$allocateIncrementingFinalizer()52 private static void $noinline$allocateIncrementingFinalizer() { 53 new IncrementingFinalizer(); 54 } 55 $noinline$allocPhantom(ReferenceQueue<Object> queue)56 public static PhantomReference $noinline$allocPhantom(ReferenceQueue<Object> queue) { 57 return new PhantomReference(new Object(), queue); 58 } 59 60 // Test that calling registerNativeAllocation triggers a GC eventually 61 // after a substantial number of registered native bytes. checkRegisterNativeAllocation()62 private static void checkRegisterNativeAllocation() throws Exception { 63 long maxMem = Runtime.getRuntime().maxMemory(); 64 int size = (int)(maxMem / 32); 65 int allocationCount = 256; 66 final long startTime = System.currentTimeMillis(); 67 68 int initialFinalizeCount = finalizeCounter.get(); 69 ReferenceQueue<Object> queue = new ReferenceQueue<Object>(); 70 ref = $noinline$allocPhantom(queue); 71 long total = 0; 72 int i; 73 for (i = 0; !ref.isEnqueued() && i < allocationCount; ++i) { 74 runtime.registerNativeAllocation(size); 75 total += size; 76 77 // Allocate a new finalizable object each time, so that we can see if anything 78 // was finalized while we were running. 79 $noinline$allocateIncrementingFinalizer(); 80 81 // Sleep a little bit to ensure not all of the calls to 82 // registerNativeAllocation complete while GC is in the process of 83 // running. 84 Thread.sleep(MAX_EXPECTED_GC_DURATION_MS / allocationCount); 85 } 86 87 // Wait up to MAX_EXPECTED_GC_DURATION_MS to give GC a chance to finish 88 // running. If the reference isn't enqueued after that, then it is 89 // pretty unlikely (though technically still possible) that GC was 90 // triggered as intended. 91 if (queue.remove(MAX_EXPECTED_GC_DURATION_MS) == null) { 92 System.out.println("GC failed to complete after " + i 93 + " iterations, is_enqueued = " + ref.isEnqueued()); 94 System.out.println(" size = " + size + ", elapsed msecs = " 95 + (System.currentTimeMillis() - startTime)); 96 System.out.println(" original maxMemory() = " + maxMem + " current maxMemory() = " 97 + Runtime.getRuntime().maxMemory()); 98 System.out.println(" Initial finalize count = " + initialFinalizeCount 99 + " current finalize count = " + finalizeCounter.get()); 100 Thread.sleep(2 * MAX_EXPECTED_GC_DURATION_MS); 101 System.out.println(" After delay, queue.poll() = " + queue.poll() 102 + " is_enqueued = " + ref.isEnqueued()); 103 System.out.println(" elapsed msecs = " + (System.currentTimeMillis() - startTime)); 104 Runtime.getRuntime().gc(); 105 System.runFinalization(); 106 System.out.println(" After GC, queue.poll() = " + queue.poll() 107 + " is_enqueued = " + ref.isEnqueued()); 108 } 109 110 while (total > 0) { 111 runtime.registerNativeFree(size); 112 total -= size; 113 } 114 } 115 116 // Call registerNativeAllocation repeatedly at a high rate to trigger the case of blocking 117 // registerNativeAllocation. Stop before we risk exhausting the finalizer timeout. triggerBlockingRegisterNativeAllocation()118 private static void triggerBlockingRegisterNativeAllocation() throws Exception { 119 final long startTime = System.currentTimeMillis(); 120 final long finalizerTimeoutMs = VMRuntime.getRuntime().getFinalizerTimeoutMs(); 121 final long quittingTime = startTime + finalizerTimeoutMs - MAX_EXPECTED_GC_DURATION_MS; 122 long maxMem = Runtime.getRuntime().maxMemory(); 123 int size = (int)(maxMem / 5); 124 int allocationCount = 10; 125 126 long total = 0; 127 for (int i = 0; i < allocationCount && System.currentTimeMillis() < quittingTime; ++i) { 128 runtime.registerNativeAllocation(size); 129 total += size; 130 } 131 132 while (total > 0) { 133 runtime.registerNativeFree(size); 134 total -= size; 135 } 136 } 137 main(String[] args)138 public static void main(String[] args) throws Exception { 139 // Test that registerNativeAllocation triggers GC. 140 // Run this a few times in a loop to reduce the chances that the test 141 // is flaky and make sure registerNativeAllocation continues to work 142 // after the first GC is triggered. 143 for (int i = 0; i < 20; ++i) { 144 checkRegisterNativeAllocation(); 145 } 146 147 // Test that we don't get a deadlock if we call 148 // registerNativeAllocation with a blocked finalizer. 149 synchronized (deadlockLock) { 150 $noinline$allocateDeadlockingFinalizer(); 151 while (!aboutToDeadlock) { 152 Runtime.getRuntime().gc(); 153 } 154 155 // Do more allocations now that the finalizer thread is deadlocked so that we force 156 // finalization and timeout. 157 triggerBlockingRegisterNativeAllocation(); 158 } 159 System.out.println("Test complete"); 160 } 161 } 162 163