1 /*
2  * Copyright (C) 2015 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.util;
18 
19 import dalvik.system.VMRuntime;
20 import sun.misc.Cleaner;
21 
22 /**
23  * A NativeAllocationRegistry is used to associate native allocations with
24  * Java objects and register them with the runtime.
25  * There are two primary benefits of registering native allocations associated
26  * with Java objects:
27  * <ol>
28  *  <li>The runtime will account for the native allocations when scheduling
29  *  garbage collection to run.</li>
30  *  <li>The runtime will arrange for the native allocation to be automatically
31  *  freed by a user-supplied function when the associated Java object becomes
32  *  unreachable.</li>
33  * </ol>
34  * A separate NativeAllocationRegistry should be instantiated for each kind
35  * of native allocation, where the kind of a native allocation consists of the
36  * native function used to free the allocation and the estimated size of the
37  * allocation. Once a NativeAllocationRegistry is instantiated, it can be
38  * used to register any number of native allocations of that kind.
39  * @hide
40  */
41 public class NativeAllocationRegistry {
42 
43     private final ClassLoader classLoader;
44     private final long freeFunction;
45     private final long size;
46 
47     /**
48      * Constructs a NativeAllocationRegistry for a particular kind of native
49      * allocation.
50      * The address of a native function that can be used to free this kind
51      * native allocation should be provided using the
52      * <code>freeFunction</code> argument. The native function should have the
53      * type:
54      * <pre>
55      *    void f(void* nativePtr);
56      * </pre>
57      * <p>
58      * The <code>classLoader</code> argument should be the class loader used
59      * to load the native library that freeFunction belongs to. This is needed
60      * to ensure the native library doesn't get unloaded before freeFunction
61      * is called.
62      * <p>
63      * The <code>size</code> should be an estimate of the total number of
64      * native bytes this kind of native allocation takes up. Different
65      * NativeAllocationRegistrys must be used to register native allocations
66      * with different estimated sizes, even if they use the same
67      * <code>freeFunction</code>.
68      * @param classLoader  ClassLoader that was used to load the native
69      *                     library freeFunction belongs to.
70      * @param freeFunction address of a native function used to free this
71      *                     kind of native allocation
72      * @param size         estimated size in bytes of this kind of native
73      *                     allocation
74      * @throws IllegalArgumentException If <code>size</code> is negative
75      */
NativeAllocationRegistry(ClassLoader classLoader, long freeFunction, long size)76     public NativeAllocationRegistry(ClassLoader classLoader, long freeFunction, long size) {
77         if (size < 0) {
78             throw new IllegalArgumentException("Invalid native allocation size: " + size);
79         }
80 
81         this.classLoader = classLoader;
82         this.freeFunction = freeFunction;
83         this.size = size;
84     }
85 
86     /**
87      * Registers a new native allocation and associated Java object with the
88      * runtime.
89      * This NativeAllocationRegistry's <code>freeFunction</code> will
90      * automatically be called with <code>nativePtr</code> as its sole
91      * argument when <code>referent</code> becomes unreachable. If you
92      * maintain copies of <code>nativePtr</code> outside
93      * <code>referent</code>, you must not access these after
94      * <code>referent</code> becomes unreachable, because they may be dangling
95      * pointers.
96      * <p>
97      * The returned Runnable can be used to free the native allocation before
98      * <code>referent</code> becomes unreachable. The runnable will have no
99      * effect if the native allocation has already been freed by the runtime
100      * or by using the runnable.
101      *
102      * @param referent      java object to associate the native allocation with
103      * @param nativePtr     address of the native allocation
104      * @return runnable to explicitly free native allocation
105      * @throws IllegalArgumentException if either referent or nativePtr is null.
106      * @throws OutOfMemoryError  if there is not enough space on the Java heap
107      *                           in which to register the allocation. In this
108      *                           case, <code>freeFunction</code> will be
109      *                           called with <code>nativePtr</code> as its
110      *                           argument before the OutOfMemoryError is
111      *                           thrown.
112      */
registerNativeAllocation(Object referent, long nativePtr)113     public Runnable registerNativeAllocation(Object referent, long nativePtr) {
114         if (referent == null) {
115             throw new IllegalArgumentException("referent is null");
116         }
117         if (nativePtr == 0) {
118             throw new IllegalArgumentException("nativePtr is null");
119         }
120 
121         try {
122             registerNativeAllocation(this.size);
123         } catch (OutOfMemoryError oome) {
124             applyFreeFunction(freeFunction, nativePtr);
125             throw oome;
126         }
127 
128         Cleaner cleaner = Cleaner.create(referent, new CleanerThunk(nativePtr));
129         return new CleanerRunner(cleaner);
130     }
131 
132     /**
133      * Interface for custom native allocation allocators used by
134      * {@link #registerNativeAllocation(Object, Allocator) registerNativeAllocation(Object, Allocator)}.
135      */
136     public interface Allocator {
137         /**
138          * Allocate a native allocation and return its address.
139          */
allocate()140         long allocate();
141     }
142 
143     /**
144      * Registers and allocates a new native allocation and associated Java
145      * object with the runtime.
146      * This can be used for registering large allocations where the underlying
147      * native allocation shouldn't be performed until it's clear there is
148      * enough space on the Java heap to register the allocation.
149      * <p>
150      * If the allocator returns null, the allocation is not registered and a
151      * null Runnable is returned.
152      *
153      * @param referent      java object to associate the native allocation with
154      * @param allocator     used to perform the underlying native allocation.
155      * @return runnable to explicitly free native allocation
156      * @throws IllegalArgumentException if referent is null.
157      * @throws OutOfMemoryError  if there is not enough space on the Java heap
158      *                           in which to register the allocation. In this
159      *                           case, the allocator will not be run.
160      */
registerNativeAllocation(Object referent, Allocator allocator)161     public Runnable registerNativeAllocation(Object referent, Allocator allocator) {
162         if (referent == null) {
163             throw new IllegalArgumentException("referent is null");
164         }
165         registerNativeAllocation(this.size);
166 
167         // Create the cleaner before running the allocator so that
168         // VMRuntime.registerNativeFree is eventually called if the allocate
169         // method throws an exception.
170         CleanerThunk thunk = new CleanerThunk();
171         Cleaner cleaner = Cleaner.create(referent, thunk);
172         long nativePtr = allocator.allocate();
173         if (nativePtr == 0) {
174             cleaner.clean();
175             return null;
176         }
177         thunk.setNativePtr(nativePtr);
178         return new CleanerRunner(cleaner);
179     }
180 
181     private class CleanerThunk implements Runnable {
182         private long nativePtr;
183 
CleanerThunk()184         public CleanerThunk() {
185             this.nativePtr = 0;
186         }
187 
CleanerThunk(long nativePtr)188         public CleanerThunk(long nativePtr) {
189             this.nativePtr = nativePtr;
190         }
191 
run()192         public void run() {
193             if (nativePtr != 0) {
194                 applyFreeFunction(freeFunction, nativePtr);
195             }
196             registerNativeFree(size);
197         }
198 
setNativePtr(long nativePtr)199         public void setNativePtr(long nativePtr) {
200             this.nativePtr = nativePtr;
201         }
202     }
203 
204     private static class CleanerRunner implements Runnable {
205         private final Cleaner cleaner;
206 
CleanerRunner(Cleaner cleaner)207         public CleanerRunner(Cleaner cleaner) {
208             this.cleaner = cleaner;
209         }
210 
run()211         public void run() {
212             cleaner.clean();
213         }
214     }
215 
216     // TODO: Change the runtime to support passing the size as a long instead
217     // of an int. For now, we clamp the size to fit.
registerNativeAllocation(long size)218     private static void registerNativeAllocation(long size) {
219         VMRuntime.getRuntime().registerNativeAllocation((int)Math.min(size, Integer.MAX_VALUE));
220     }
221 
registerNativeFree(long size)222     private static void registerNativeFree(long size) {
223         VMRuntime.getRuntime().registerNativeFree((int)Math.min(size, Integer.MAX_VALUE));
224     }
225 
226     /**
227      * Calls <code>freeFunction</code>(<code>nativePtr</code>).
228      * Provided as a convenience in the case where you wish to manually free a
229      * native allocation using a <code>freeFunction</code> without using a
230      * NativeAllocationRegistry.
231      */
applyFreeFunction(long freeFunction, long nativePtr)232     public static native void applyFreeFunction(long freeFunction, long nativePtr);
233 }
234 
235