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