blob: 6731683101e1db7d74038f8b6aa48f6444c56c89 [file] [log] [blame]
/*
* 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");
}
}