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 {
finalize()33         protected void finalize() throws Exception {
34             aboutToDeadlock = true;
35             synchronized (deadlockLock) { }
36         }
37     }
38 
allocateDeadlockingFinalizer()39     private static void allocateDeadlockingFinalizer() {
40         new DeadlockingFinalizer();
41     }
42 
allocPhantom(ReferenceQueue<Object> queue)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.
checkRegisterNativeAllocation()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.
triggerBlockingRegisterNativeAllocation()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 
main(String[] args)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