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 dalvik.system.VMRuntime; 21 22 public class Main { 23 static Object deadlockLock = new Object(); 24 static VMRuntime runtime = VMRuntime.getRuntime(); 25 static volatile boolean aboutToDeadlock = false; 26 static final long MAX_EXPECTED_GC_DURATION_MS = 2000; 27 28 // Save ref as a static field to ensure it doesn't get GC'd before the 29 // referent is enqueued. 30 static PhantomReference ref = null; 31 32 static class DeadlockingFinalizer { 33 protected void finalize() throws Exception { 34 aboutToDeadlock = true; 35 synchronized (deadlockLock) { } 36 } 37 } 38 39 private static void allocateDeadlockingFinalizer() { 40 new DeadlockingFinalizer(); 41 } 42 43 public static PhantomReference allocPhantom(ReferenceQueue<Object> queue) { 44 return new PhantomReference(new Object(), queue); 45 } 46 47 // Test that calling registerNativeAllocation triggers a GC eventually 48 // after a substantial number of registered native bytes. 49 private static void checkRegisterNativeAllocation() throws Exception { 50 long maxMem = Runtime.getRuntime().maxMemory(); 51 int size = (int)(maxMem / 32); 52 int allocationCount = 256; 53 54 ReferenceQueue<Object> queue = new ReferenceQueue<Object>(); 55 ref = allocPhantom(queue); 56 long total = 0; 57 for (int i = 0; !ref.isEnqueued() && i < allocationCount; ++i) { 58 runtime.registerNativeAllocation(size); 59 total += size; 60 61 // Sleep a little bit to ensure not all of the calls to 62 // registerNativeAllocation complete while GC is in the process of 63 // running. 64 Thread.sleep(MAX_EXPECTED_GC_DURATION_MS / allocationCount); 65 } 66 67 // Wait up to MAX_EXPECTED_GC_DURATION_MS to give GC a chance to finish 68 // running. If the reference isn't enqueued after that, then it is 69 // pretty unlikely (though technically still possible) that GC was 70 // triggered as intended. 71 if (queue.remove(MAX_EXPECTED_GC_DURATION_MS) == null) { 72 throw new RuntimeException("GC failed to complete"); 73 } 74 75 while (total > 0) { 76 runtime.registerNativeFree(size); 77 total -= size; 78 } 79 } 80 81 // Call registerNativeAllocation repeatedly at a high rate to trigger the case of blocking 82 // registerNativeAllocation. Stop before we risk exhausting the finalizer timeout. 83 private static void triggerBlockingRegisterNativeAllocation() throws Exception { 84 final long startTime = System.currentTimeMillis(); 85 final long finalizerTimeoutMs = VMRuntime.getRuntime().getFinalizerTimeoutMs(); 86 final long quittingTime = startTime + finalizerTimeoutMs - MAX_EXPECTED_GC_DURATION_MS; 87 long maxMem = Runtime.getRuntime().maxMemory(); 88 int size = (int)(maxMem / 5); 89 int allocationCount = 10; 90 91 long total = 0; 92 for (int i = 0; i < allocationCount && System.currentTimeMillis() < quittingTime; ++i) { 93 runtime.registerNativeAllocation(size); 94 total += size; 95 } 96 97 while (total > 0) { 98 runtime.registerNativeFree(size); 99 total -= size; 100 } 101 } 102 103 public static void main(String[] args) throws Exception { 104 // Test that registerNativeAllocation triggers GC. 105 // Run this a few times in a loop to reduce the chances that the test 106 // is flaky and make sure registerNativeAllocation continues to work 107 // after the first GC is triggered. 108 for (int i = 0; i < 20; ++i) { 109 checkRegisterNativeAllocation(); 110 } 111 112 // Test that we don't get a deadlock if we call 113 // registerNativeAllocation with a blocked finalizer. 114 synchronized (deadlockLock) { 115 allocateDeadlockingFinalizer(); 116 while (!aboutToDeadlock) { 117 Runtime.getRuntime().gc(); 118 } 119 120 // Do more allocations now that the finalizer thread is deadlocked so that we force 121 // finalization and timeout. 122 triggerBlockingRegisterNativeAllocation(); 123 } 124 System.out.println("Test complete"); 125 } 126 } 127 128