/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package libcore.util; import dalvik.system.VMRuntime; import sun.misc.Cleaner; /** * A NativeAllocationRegistry is used to associate native allocations with * Java objects and register them with the runtime. * There are two primary benefits of registering native allocations associated * with Java objects: *
freeFunction
argument. The native function should have the
* type:
* * void f(void* nativePtr); **
* The classLoader
argument should be the class loader used
* to load the native library that freeFunction belongs to. This is needed
* to ensure the native library doesn't get unloaded before freeFunction
* is called.
*
* The size
should be an estimate of the total number of
* native bytes this kind of native allocation takes up. Different
* NativeAllocationRegistrys must be used to register native allocations
* with different estimated sizes, even if they use the same
* freeFunction
.
* @param classLoader ClassLoader that was used to load the native
* library freeFunction belongs to.
* @param freeFunction address of a native function used to free this
* kind of native allocation
* @param size estimated size in bytes of this kind of native
* allocation
* @throws IllegalArgumentException If size
is negative
*/
public NativeAllocationRegistry(ClassLoader classLoader, long freeFunction, long size) {
if (size < 0) {
throw new IllegalArgumentException("Invalid native allocation size: " + size);
}
this.classLoader = classLoader;
this.freeFunction = freeFunction;
this.size = size;
}
/**
* Registers a new native allocation and associated Java object with the
* runtime.
* This NativeAllocationRegistry's freeFunction
will
* automatically be called with nativePtr
as its sole
* argument when referent
becomes unreachable. If you
* maintain copies of nativePtr
outside
* referent
, you must not access these after
* referent
becomes unreachable, because they may be dangling
* pointers.
*
* The returned Runnable can be used to free the native allocation before
* referent
becomes unreachable. The runnable will have no
* effect if the native allocation has already been freed by the runtime
* or by using the runnable.
*
* @param referent java object to associate the native allocation with
* @param nativePtr address of the native allocation
* @return runnable to explicitly free native allocation
* @throws IllegalArgumentException if either referent or nativePtr is null.
* @throws OutOfMemoryError if there is not enough space on the Java heap
* in which to register the allocation. In this
* case, freeFunction
will be
* called with nativePtr
as its
* argument before the OutOfMemoryError is
* thrown.
*/
public Runnable registerNativeAllocation(Object referent, long nativePtr) {
if (referent == null) {
throw new IllegalArgumentException("referent is null");
}
if (nativePtr == 0) {
throw new IllegalArgumentException("nativePtr is null");
}
try {
registerNativeAllocation(this.size);
} catch (OutOfMemoryError oome) {
applyFreeFunction(freeFunction, nativePtr);
throw oome;
}
Cleaner cleaner = Cleaner.create(referent, new CleanerThunk(nativePtr));
return new CleanerRunner(cleaner);
}
/**
* Interface for custom native allocation allocators used by
* {@link #registerNativeAllocation(Object, Allocator) registerNativeAllocation(Object, Allocator)}.
*/
public interface Allocator {
/**
* Allocate a native allocation and return its address.
*/
long allocate();
}
/**
* Registers and allocates a new native allocation and associated Java
* object with the runtime.
* This can be used for registering large allocations where the underlying
* native allocation shouldn't be performed until it's clear there is
* enough space on the Java heap to register the allocation.
*
* If the allocator returns null, the allocation is not registered and a
* null Runnable is returned.
*
* @param referent java object to associate the native allocation with
* @param allocator used to perform the underlying native allocation.
* @return runnable to explicitly free native allocation
* @throws IllegalArgumentException if referent is null.
* @throws OutOfMemoryError if there is not enough space on the Java heap
* in which to register the allocation. In this
* case, the allocator will not be run.
*/
public Runnable registerNativeAllocation(Object referent, Allocator allocator) {
if (referent == null) {
throw new IllegalArgumentException("referent is null");
}
registerNativeAllocation(this.size);
// Create the cleaner before running the allocator so that
// VMRuntime.registerNativeFree is eventually called if the allocate
// method throws an exception.
CleanerThunk thunk = new CleanerThunk();
Cleaner cleaner = Cleaner.create(referent, thunk);
long nativePtr = allocator.allocate();
if (nativePtr == 0) {
cleaner.clean();
return null;
}
thunk.setNativePtr(nativePtr);
return new CleanerRunner(cleaner);
}
private class CleanerThunk implements Runnable {
private long nativePtr;
public CleanerThunk() {
this.nativePtr = 0;
}
public CleanerThunk(long nativePtr) {
this.nativePtr = nativePtr;
}
public void run() {
if (nativePtr != 0) {
applyFreeFunction(freeFunction, nativePtr);
}
registerNativeFree(size);
}
public void setNativePtr(long nativePtr) {
this.nativePtr = nativePtr;
}
}
private static class CleanerRunner implements Runnable {
private final Cleaner cleaner;
public CleanerRunner(Cleaner cleaner) {
this.cleaner = cleaner;
}
public void run() {
cleaner.clean();
}
}
// TODO: Change the runtime to support passing the size as a long instead
// of an int. For now, we clamp the size to fit.
private static void registerNativeAllocation(long size) {
VMRuntime.getRuntime().registerNativeAllocation((int)Math.min(size, Integer.MAX_VALUE));
}
private static void registerNativeFree(long size) {
VMRuntime.getRuntime().registerNativeFree((int)Math.min(size, Integer.MAX_VALUE));
}
/**
* Calls freeFunction
(nativePtr
).
* Provided as a convenience in the case where you wish to manually free a
* native allocation using a freeFunction
without using a
* NativeAllocationRegistry.
*/
public static native void applyFreeFunction(long freeFunction, long nativePtr);
}