blob: 9190452de0ec383d11d9b205ad176247ff2c62ca [file] [log] [blame]
/*
* Copyright (C) 2016 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.
*/
package libcore.libcore.util;
import junit.framework.TestCase;
import libcore.util.NativeAllocationRegistry;
public class NativeAllocationRegistryTest extends TestCase {
static {
System.loadLibrary("javacoretests");
}
private ClassLoader classLoader = NativeAllocationRegistryTest.class.getClassLoader();
private static class TestConfig {
public boolean treatAsMalloced;
public boolean shareRegistry;
public TestConfig(boolean treatAsMalloced, boolean shareRegistry) {
this.shareRegistry = shareRegistry;
}
}
private static class Allocation {
public byte[] javaAllocation;
public long nativeAllocation;
}
// Verify that NativeAllocations and their referents are freed before we run
// out of space for new allocations.
private void testNativeAllocation(TestConfig config) {
if (isNativeBridgedABI()) {
// 1. This test is intended to test platform internals, not public API.
// 2. The test would fail under native bridge as a side effect of how the tests work:
// - The tests run using the app architecture instead of the platform architecture
// - That scenario will never happen in practice due to (1)
// 3. This leaves a hole in testing for the case of native bridge, due to limitations
// in the testing infrastructure from (2).
System.logI("Skipping test for native bridged ABI");
return;
}
Runtime.getRuntime().gc();
System.runFinalization();
long nativeBytes = getNumNativeBytesAllocated();
assertEquals("Native bytes already allocated", 0, nativeBytes);
long max = Runtime.getRuntime().maxMemory();
long total = Runtime.getRuntime().totalMemory();
int size = 1024 * 1024;
final int nativeSize = size / 2;
int javaSize = size / 2;
int expectedMaxNumAllocations = (int)(max-total) / javaSize;
int numSavedAllocations = expectedMaxNumAllocations / 2;
Allocation[] saved = new Allocation[numSavedAllocations];
NativeAllocationRegistry registry = null;
int numAllocationsToSimulate = 10 * expectedMaxNumAllocations;
// Allocate more native allocations than will fit in memory. This should
// not throw OutOfMemoryError because the few allocations we save
// references to should easily fit.
for (int i = 0; i < numAllocationsToSimulate; i++) {
if (!config.shareRegistry || registry == null) {
if (config.treatAsMalloced) {
registry = NativeAllocationRegistry.createMalloced(
classLoader, getNativeFinalizer(), nativeSize);
} else {
registry = NativeAllocationRegistry.createNonmalloced(
classLoader, getNativeFinalizer(), nativeSize);
}
}
final Allocation alloc = new Allocation();
alloc.javaAllocation = new byte[javaSize];
alloc.nativeAllocation = doNativeAllocation(nativeSize);
registry.registerNativeAllocation(alloc, alloc.nativeAllocation);
saved[i % numSavedAllocations] = alloc;
}
// Verify most of the allocations have been freed. Since we use fairly large Java
// objects, this doesn't test the GC triggering effect; we do that elsewhere.
//
// Since native and java objects have the same size, and we can only have max Java bytes
// in use, there should ideally be no more than max native bytes in use, once all enqueued
// deallocations have been processed. We call runFinalization() to make sure that the
// ReferenceQueueDaemon has processed all pending requests, and then check.
// (runFinalization() isn't documented to guarantee this, but it waits for a sentinel
// object to make it all the way through the pending reference queue, and hence has that
// effect.)
//
// However the garbage collector enqueues references asynchronously, by enqueuing
// another heap task. If the GC runs before we finish our allocation, but reference
// enqueueing is delayed, and runFinalization() runs between the time the GC reclaims
// memory and the references are enqueued, then runFinalization() may complete
// immediately, and further allocation may have occurred between the GC and the invocation
// of runFinalization(). Thus, under unlikely conditions, we may see up to twice as much
// native memory as the Java heap, and that's the actual condition we test.
System.runFinalization();
nativeBytes = getNumNativeBytesAllocated();
assertTrue("Excessive native bytes still allocated (" + nativeBytes + ")"
+ " given max memory of (" + max + ")", nativeBytes <= 2 * max);
// Check that the array is fully populated, and sufficiently many native bytes
// are live.
long nativeReachableBytes = numSavedAllocations * nativeSize;
for (int i = 0; i < numSavedAllocations; i++) {
assertNotNull(saved[i]);
assertNotNull(saved[i].javaAllocation);
assertTrue(saved[i].nativeAllocation != 0);
}
assertTrue("Too few native bytes still allocated (" + nativeBytes + "); "
+ nativeReachableBytes + " bytes are reachable",
nativeBytes >= nativeReachableBytes);
}
public void testNativeAllocationNonmallocNoSharedRegistry() {
testNativeAllocation(new TestConfig(false, false));
}
public void testNativeAllocationNonmallocSharedRegistry() {
testNativeAllocation(new TestConfig(false, true));
}
public void testNativeAllocationMallocNoSharedRegistry() {
testNativeAllocation(new TestConfig(true, false));
}
public void testNativeAllocationMallocSharedRegistry() {
testNativeAllocation(new TestConfig(true, true));
}
public void testBadSize() {
assertThrowsIllegalArgumentException(new Runnable() {
public void run() {
NativeAllocationRegistry registry = new NativeAllocationRegistry(
classLoader, getNativeFinalizer(), -8);
}
});
}
public void testEarlyFree() {
if (isNativeBridgedABI()) {
// See the explanation in testNativeAllocation.
System.logI("Skipping test for native bridged ABI");
return;
}
long size = 1234;
NativeAllocationRegistry registry
= new NativeAllocationRegistry(classLoader, getNativeFinalizer(), size);
long nativePtr = doNativeAllocation(size);
Object referent = new Object();
Runnable cleaner = registry.registerNativeAllocation(referent, nativePtr);
long numBytesAllocatedBeforeClean = getNumNativeBytesAllocated();
// Running the cleaner should cause the native finalizer to run.
cleaner.run();
long numBytesAllocatedAfterClean = getNumNativeBytesAllocated();
assertEquals(numBytesAllocatedBeforeClean - size, numBytesAllocatedAfterClean);
// Running the cleaner again should have no effect.
cleaner.run();
assertEquals(numBytesAllocatedAfterClean, getNumNativeBytesAllocated());
// There shouldn't be any problems when the referent object is GC'd.
referent = null;
Runtime.getRuntime().gc();
}
public void testApplyFreeFunction() {
if (isNativeBridgedABI()) {
// See the explanation in testNativeAllocation.
System.logI("Skipping test for native bridged ABI");
return;
}
long size = 1234;
long nativePtr = doNativeAllocation(size);
long numBytesAllocatedBeforeFree = getNumNativeBytesAllocated();
// Applying the free function should cause the native finalizer to run.
NativeAllocationRegistry.applyFreeFunction(getNativeFinalizer(), nativePtr);
long numBytesAllocatedAfterFree = getNumNativeBytesAllocated();
assertEquals(numBytesAllocatedBeforeFree - size, numBytesAllocatedAfterFree);
}
public void testNullArguments() {
final NativeAllocationRegistry registry
= new NativeAllocationRegistry(classLoader, getNativeFinalizer(), 1024);
final long fakeNativePtr = 0x1;
final Object referent = new Object();
// referent should not be null
assertThrowsIllegalArgumentException(new Runnable() {
public void run() {
registry.registerNativeAllocation(null, fakeNativePtr);
}
});
// nativePtr should not be null
assertThrowsIllegalArgumentException(new Runnable() {
public void run() {
registry.registerNativeAllocation(referent, 0);
}
});
}
private static void assertThrowsIllegalArgumentException(Runnable runnable) {
try {
runnable.run();
} catch (IllegalArgumentException ex) {
return;
}
fail("Expected IllegalArgumentException, but no exception was thrown.");
}
private static native boolean isNativeBridgedABI();
private static native long getNativeFinalizer();
private static native long doNativeAllocation(long size);
private static native long getNumNativeBytesAllocated();
}