xref: /aosp_15_r20/art/test/004-NativeAllocations/src-art/Main.java (revision 795d594fd825385562da6b089ea9b2033f3abf5a)
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