1 /* 2 * Copyright (C) 2016 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 package libcore.libcore.util; 18 19 import junit.framework.TestCase; 20 21 import libcore.util.NativeAllocationRegistry; 22 23 public class NativeAllocationRegistryTest extends TestCase { 24 25 static { 26 System.loadLibrary("javacoretests"); 27 } 28 29 private ClassLoader classLoader = NativeAllocationRegistryTest.class.getClassLoader(); 30 31 private static class TestConfig { 32 public boolean treatAsMalloced; 33 public boolean shareRegistry; 34 TestConfig(boolean treatAsMalloced, boolean shareRegistry)35 public TestConfig(boolean treatAsMalloced, boolean shareRegistry) { 36 this.shareRegistry = shareRegistry; 37 } 38 } 39 40 private static class Allocation { 41 public byte[] javaAllocation; 42 public long nativeAllocation; 43 } 44 45 // Verify that NativeAllocations and their referents are freed before we run 46 // out of space for new allocations. testNativeAllocation(TestConfig config)47 private void testNativeAllocation(TestConfig config) { 48 if (isNativeBridgedABI()) { 49 // 1. This test is intended to test platform internals, not public API. 50 // 2. The test would fail under native bridge as a side effect of how the tests work: 51 // - The tests run using the app architecture instead of the platform architecture 52 // - That scenario will never happen in practice due to (1) 53 // 3. This leaves a hole in testing for the case of native bridge, due to limitations 54 // in the testing infrastructure from (2). 55 System.logI("Skipping test for native bridged ABI"); 56 return; 57 } 58 Runtime.getRuntime().gc(); 59 System.runFinalization(); 60 long nativeBytes = getNumNativeBytesAllocated(); 61 assertEquals("Native bytes already allocated", 0, nativeBytes); 62 long max = Runtime.getRuntime().maxMemory(); 63 long total = Runtime.getRuntime().totalMemory(); 64 int size = 1024 * 1024; 65 final int nativeSize = size / 2; 66 int javaSize = size / 2; 67 int expectedMaxNumAllocations = (int)(max-total) / javaSize; 68 int numSavedAllocations = expectedMaxNumAllocations / 2; 69 Allocation[] saved = new Allocation[numSavedAllocations]; 70 71 NativeAllocationRegistry registry = null; 72 int numAllocationsToSimulate = 10 * expectedMaxNumAllocations; 73 74 // Allocate more native allocations than will fit in memory. This should 75 // not throw OutOfMemoryError because the few allocations we save 76 // references to should easily fit. 77 for (int i = 0; i < numAllocationsToSimulate; i++) { 78 if (!config.shareRegistry || registry == null) { 79 if (config.treatAsMalloced) { 80 registry = NativeAllocationRegistry.createMalloced( 81 classLoader, getNativeFinalizer(), nativeSize); 82 } else { 83 registry = NativeAllocationRegistry.createNonmalloced( 84 classLoader, getNativeFinalizer(), nativeSize); 85 } 86 } 87 88 final Allocation alloc = new Allocation(); 89 alloc.javaAllocation = new byte[javaSize]; 90 alloc.nativeAllocation = doNativeAllocation(nativeSize); 91 registry.registerNativeAllocation(alloc, alloc.nativeAllocation); 92 93 saved[i % numSavedAllocations] = alloc; 94 } 95 96 // Verify most of the allocations have been freed. Since we use fairly large Java 97 // objects, this doesn't test the GC triggering effect; we do that elsewhere. 98 // 99 // Since native and java objects have the same size, and we can only have max Java bytes 100 // in use, there should ideally be no more than max native bytes in use, once all enqueued 101 // deallocations have been processed. We call runFinalization() to make sure that the 102 // ReferenceQueueDaemon has processed all pending requests, and then check. 103 // (runFinalization() isn't documented to guarantee this, but it waits for a sentinel 104 // object to make it all the way through the pending reference queue, and hence has that 105 // effect.) 106 // 107 // However the garbage collector enqueues references asynchronously, by enqueuing 108 // another heap task. If the GC runs before we finish our allocation, but reference 109 // enqueueing is delayed, and runFinalization() runs between the time the GC reclaims 110 // memory and the references are enqueued, then runFinalization() may complete 111 // immediately, and further allocation may have occurred between the GC and the invocation 112 // of runFinalization(). Thus, under unlikely conditions, we may see up to twice as much 113 // native memory as the Java heap, and that's the actual condition we test. 114 System.runFinalization(); 115 nativeBytes = getNumNativeBytesAllocated(); 116 assertTrue("Excessive native bytes still allocated (" + nativeBytes + ")" 117 + " given max memory of (" + max + ")", nativeBytes <= 2 * max); 118 // Check that the array is fully populated, and sufficiently many native bytes 119 // are live. 120 long nativeReachableBytes = numSavedAllocations * nativeSize; 121 for (int i = 0; i < numSavedAllocations; i++) { 122 assertNotNull(saved[i]); 123 assertNotNull(saved[i].javaAllocation); 124 assertTrue(saved[i].nativeAllocation != 0); 125 } 126 assertTrue("Too few native bytes still allocated (" + nativeBytes + "); " 127 + nativeReachableBytes + " bytes are reachable", 128 nativeBytes >= nativeReachableBytes); 129 } 130 testNativeAllocationNonmallocNoSharedRegistry()131 public void testNativeAllocationNonmallocNoSharedRegistry() { 132 testNativeAllocation(new TestConfig(false, false)); 133 } 134 testNativeAllocationNonmallocSharedRegistry()135 public void testNativeAllocationNonmallocSharedRegistry() { 136 testNativeAllocation(new TestConfig(false, true)); 137 } 138 testNativeAllocationMallocNoSharedRegistry()139 public void testNativeAllocationMallocNoSharedRegistry() { 140 testNativeAllocation(new TestConfig(true, false)); 141 } 142 testNativeAllocationMallocSharedRegistry()143 public void testNativeAllocationMallocSharedRegistry() { 144 testNativeAllocation(new TestConfig(true, true)); 145 } 146 testBadSize()147 public void testBadSize() { 148 assertThrowsIllegalArgumentException(new Runnable() { 149 public void run() { 150 NativeAllocationRegistry registry = new NativeAllocationRegistry( 151 classLoader, getNativeFinalizer(), -8); 152 } 153 }); 154 } 155 testEarlyFree()156 public void testEarlyFree() { 157 if (isNativeBridgedABI()) { 158 // See the explanation in testNativeAllocation. 159 System.logI("Skipping test for native bridged ABI"); 160 return; 161 } 162 long size = 1234; 163 NativeAllocationRegistry registry 164 = new NativeAllocationRegistry(classLoader, getNativeFinalizer(), size); 165 long nativePtr = doNativeAllocation(size); 166 Object referent = new Object(); 167 Runnable cleaner = registry.registerNativeAllocation(referent, nativePtr); 168 long numBytesAllocatedBeforeClean = getNumNativeBytesAllocated(); 169 170 // Running the cleaner should cause the native finalizer to run. 171 cleaner.run(); 172 long numBytesAllocatedAfterClean = getNumNativeBytesAllocated(); 173 assertEquals(numBytesAllocatedBeforeClean - size, numBytesAllocatedAfterClean); 174 175 // Running the cleaner again should have no effect. 176 cleaner.run(); 177 assertEquals(numBytesAllocatedAfterClean, getNumNativeBytesAllocated()); 178 179 // There shouldn't be any problems when the referent object is GC'd. 180 referent = null; 181 Runtime.getRuntime().gc(); 182 } 183 testApplyFreeFunction()184 public void testApplyFreeFunction() { 185 if (isNativeBridgedABI()) { 186 // See the explanation in testNativeAllocation. 187 System.logI("Skipping test for native bridged ABI"); 188 return; 189 } 190 long size = 1234; 191 long nativePtr = doNativeAllocation(size); 192 long numBytesAllocatedBeforeFree = getNumNativeBytesAllocated(); 193 194 // Applying the free function should cause the native finalizer to run. 195 NativeAllocationRegistry.applyFreeFunction(getNativeFinalizer(), nativePtr); 196 long numBytesAllocatedAfterFree = getNumNativeBytesAllocated(); 197 assertEquals(numBytesAllocatedBeforeFree - size, numBytesAllocatedAfterFree); 198 } 199 testNullArguments()200 public void testNullArguments() { 201 final NativeAllocationRegistry registry 202 = new NativeAllocationRegistry(classLoader, getNativeFinalizer(), 1024); 203 final long fakeNativePtr = 0x1; 204 final Object referent = new Object(); 205 206 // referent should not be null 207 assertThrowsIllegalArgumentException(new Runnable() { 208 public void run() { 209 registry.registerNativeAllocation(null, fakeNativePtr); 210 } 211 }); 212 213 // nativePtr should not be null 214 assertThrowsIllegalArgumentException(new Runnable() { 215 public void run() { 216 registry.registerNativeAllocation(referent, 0); 217 } 218 }); 219 } 220 assertThrowsIllegalArgumentException(Runnable runnable)221 private static void assertThrowsIllegalArgumentException(Runnable runnable) { 222 try { 223 runnable.run(); 224 } catch (IllegalArgumentException ex) { 225 return; 226 } 227 fail("Expected IllegalArgumentException, but no exception was thrown."); 228 } 229 isNativeBridgedABI()230 private static native boolean isNativeBridgedABI(); getNativeFinalizer()231 private static native long getNativeFinalizer(); doNativeAllocation(long size)232 private static native long doNativeAllocation(long size); getNumNativeBytesAllocated()233 private static native long getNumNativeBytesAllocated(); 234 } 235