Home | History | Annotate | Download | only in util
      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      */
     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      * <p>
    102      * WARNING: This unconditionally takes ownership, i.e. deallocation
    103      * responsibility of nativePtr. nativePtr will be DEALLOCATED IMMEDIATELY
    104      * if the registration attempt throws an exception (other than one reporting
    105      * a programming error).
    106      *
    107      * @param referent      Non-null java object to associate the native allocation with
    108      * @param nativePtr     Non-zero address of the native allocation
    109      * @return runnable to explicitly free native allocation
    110      * @throws IllegalArgumentException if either referent or nativePtr is null.
    111      * @throws OutOfMemoryError  if there is not enough space on the Java heap
    112      *                           in which to register the allocation. In this
    113      *                           case, <code>freeFunction</code> will be
    114      *                           called with <code>nativePtr</code> as its
    115      *                           argument before the OutOfMemoryError is
    116      *                           thrown.
    117      */
    118     public Runnable registerNativeAllocation(Object referent, long nativePtr) {
    119         if (referent == null) {
    120             throw new IllegalArgumentException("referent is null");
    121         }
    122         if (nativePtr == 0) {
    123             throw new IllegalArgumentException("nativePtr is null");
    124         }
    125 
    126         CleanerThunk thunk;
    127         CleanerRunner result;
    128         try {
    129             thunk = new CleanerThunk();
    130             Cleaner cleaner = Cleaner.create(referent, thunk);
    131             result = new CleanerRunner(cleaner);
    132             registerNativeAllocation(this.size);
    133         } catch (VirtualMachineError vme /* probably OutOfMemoryError */) {
    134             applyFreeFunction(freeFunction, nativePtr);
    135             throw vme;
    136         } // Other exceptions are impossible.
    137         // Enable the cleaner only after we can no longer throw anything, including OOME.
    138         thunk.setNativePtr(nativePtr);
    139         return result;
    140     }
    141 
    142     /**
    143      * Interface for custom native allocation allocators used by
    144      * {@link #registerNativeAllocation(Object, Allocator) registerNativeAllocation(Object, Allocator)}.
    145      */
    146     public interface Allocator {
    147         /**
    148          * Allocate a native allocation and return its address.
    149          */
    150         long allocate();
    151     }
    152 
    153     /**
    154      * Registers and allocates a new native allocation and associated Java
    155      * object with the runtime.
    156      * This can be used for registering large allocations where the underlying
    157      * native allocation shouldn't be performed until it's clear there is
    158      * enough space on the Java heap to register the allocation.
    159      * <p>
    160      * If the allocator returns null, the allocation is not registered and a
    161      * null Runnable is returned.
    162      *
    163      * @param referent      Non-null java object to associate the native allocation with
    164      * @param allocator     used to perform the underlying native allocation.
    165      * @return runnable to explicitly free native allocation
    166      * @throws IllegalArgumentException if referent is null.
    167      * @throws OutOfMemoryError  if there is not enough space on the Java heap
    168      *                           in which to register the allocation. In this
    169      *                           case, the allocator will not be run.
    170      */
    171     public Runnable registerNativeAllocation(Object referent, Allocator allocator) {
    172         if (referent == null) {
    173             throw new IllegalArgumentException("referent is null");
    174         }
    175 
    176         // Create the cleaner before running the allocator so that
    177         // VMRuntime.registerNativeFree is eventually called if the allocate
    178         // method throws an exception.
    179         CleanerThunk thunk = new CleanerThunk();
    180         Cleaner cleaner = Cleaner.create(referent, thunk);
    181         CleanerRunner result = new CleanerRunner(cleaner);
    182         long nativePtr = allocator.allocate();
    183         if (nativePtr == 0) {
    184             cleaner.clean();
    185             return null;
    186         }
    187         registerNativeAllocation(this.size);
    188         thunk.setNativePtr(nativePtr);
    189         return result;
    190     }
    191 
    192     private class CleanerThunk implements Runnable {
    193         private long nativePtr;
    194 
    195         public CleanerThunk() {
    196             this.nativePtr = 0;
    197         }
    198 
    199         public void run() {
    200             if (nativePtr != 0) {
    201                 applyFreeFunction(freeFunction, nativePtr);
    202                 registerNativeFree(size);
    203             }
    204         }
    205 
    206         public void setNativePtr(long nativePtr) {
    207             this.nativePtr = nativePtr;
    208         }
    209     }
    210 
    211     private static class CleanerRunner implements Runnable {
    212         private final Cleaner cleaner;
    213 
    214         public CleanerRunner(Cleaner cleaner) {
    215             this.cleaner = cleaner;
    216         }
    217 
    218         public void run() {
    219             cleaner.clean();
    220         }
    221     }
    222 
    223     // TODO: Change the runtime to support passing the size as a long instead
    224     // of an int. For now, we clamp the size to fit.
    225     private static void registerNativeAllocation(long size) {
    226         VMRuntime.getRuntime().registerNativeAllocation((int)Math.min(size, Integer.MAX_VALUE));
    227     }
    228 
    229     private static void registerNativeFree(long size) {
    230         VMRuntime.getRuntime().registerNativeFree((int)Math.min(size, Integer.MAX_VALUE));
    231     }
    232 
    233     /**
    234      * Calls <code>freeFunction</code>(<code>nativePtr</code>).
    235      * Provided as a convenience in the case where you wish to manually free a
    236      * native allocation using a <code>freeFunction</code> without using a
    237      * NativeAllocationRegistry.
    238      */
    239     public static native void applyFreeFunction(long freeFunction, long nativePtr);
    240 }
    241 
    242