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