/*
* 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:
* <ol>
* <li>The runtime will account for the native allocations when scheduling
* garbage collection to run.</li>
* <li>The runtime will arrange for the native allocation to be automatically
* freed by a user-supplied function when the associated Java object becomes
* unreachable.</li>
* </ol>
* A separate NativeAllocationRegistry should be instantiated for each kind
* of native allocation, where the kind of a native allocation consists of the
* native function used to free the allocation and the estimated size of the
* allocation. Once a NativeAllocationRegistry is instantiated, it can be
* used to register any number of native allocations of that kind.
* @hide
*/
public class NativeAllocationRegistry {
private final ClassLoader classLoader;
private final long freeFunction;
private final long size;
/**
* Constructs a NativeAllocationRegistry for a particular kind of native
* allocation.
* The address of a native function that can be used to free this kind
* native allocation should be provided using the
* <code>freeFunction</code> argument. The native function should have the
* type:
* <pre>
* void f(void* nativePtr);
* </pre>
* <p>
* The <code>classLoader</code> 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.
* <p>
* The <code>size</code> 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
* <code>freeFunction</code>.
* @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 <code>size</code> 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 <code>freeFunction</code> will
* automatically be called with <code>nativePtr</code> as its sole
* argument when <code>referent</code> becomes unreachable. If you
* maintain copies of <code>nativePtr</code> outside
* <code>referent</code>, you must not access these after
* <code>referent</code> becomes unreachable, because they may be dangling
* pointers.
* <p>
* The returned Runnable can be used to free the native allocation before
* <code>referent</code> 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, <code>freeFunction</code> will be
* called with <code>nativePtr</code> 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.
* <p>
* 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 <code>freeFunction</code>(<code>nativePtr</code>).
* Provided as a convenience in the case where you wish to manually free a
* native allocation using a <code>freeFunction</code> without using a
* NativeAllocationRegistry.
*/
public static native void applyFreeFunction(long freeFunction, long nativePtr);
}