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