• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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