blob: d4e60992a73e7035c1d63ecef363dab7c96d9dd2 [file] [log] [blame]
/*
* 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 static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
import android.annotation.SystemApi;
import dalvik.system.VMRuntime;
import sun.misc.Cleaner;
import java.lang.ref.Reference;
import libcore.util.NonNull;
/**
* 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
*/
@SystemApi(client = MODULE_LIBRARIES)
@libcore.api.IntraCoreApi
public class NativeAllocationRegistry {
private final ClassLoader classLoader;
// Pointer to native deallocation function of type void f(void* freeFunction).
private final long freeFunction;
// The size of the registered native objects. This can be, and usually is, approximate.
// The least significant bit is one iff the object was allocated primarily with system
// malloc().
// This field is examined by ahat and other tools. We chose this encoding of the "is_malloced"
// information to (a) allow existing readers to continue to work with minimal confusion,
// and (b) to avoid adding a field to NativeAllocationRegistry objects.
private final long size;
// Bit mask for "is_malloced" information.
private static final long IS_MALLOCED = 0x1;
// Assumed size for malloced objects that don't specify a size.
// We use an even value close to 100 that is unlikely to be explicitly provided.
private static final long DEFAULT_SIZE = 98;
/**
* Return a {@link NativeAllocationRegistry} for native memory that is mostly
* allocated by means other than the system memory allocator. For example,
* the memory may be allocated directly with mmap.
* @param classLoader ClassLoader that was used to load the native
* library defining freeFunction.
* This ensures that the the native library isn't unloaded
* before {@code freeFunction} is called.
* @param freeFunction address of a native function of type
* {@code void f(void* nativePtr)} used to free this
* kind of native allocation
* @param size estimated size in bytes of the part of the described
* native memory that is not allocated with system malloc.
* Used as input to the garbage collector triggering algorithm,
* and by heap analysis tools.
* Approximate values are acceptable.
* @return allocated {@link NativeAllocationRegistry}
* @throws IllegalArgumentException If {@code size} is negative
*
* @hide
*/
@SystemApi(client = MODULE_LIBRARIES)
public static NativeAllocationRegistry createNonmalloced(
@NonNull ClassLoader classLoader, long freeFunction, long size) {
return new NativeAllocationRegistry(classLoader, freeFunction, size, false);
}
/**
* Return a {@link NativeAllocationRegistry} for native memory that is mostly
* allocated by the system memory allocator.
* For example, the memory may be allocated directly with new or malloc.
* <p>
* The native function should have the type:
* <pre>
* void f(void* nativePtr);
* </pre>
* <p>
* @param classLoader ClassLoader that was used to load the native
* library {@code freeFunction} belongs to.
* @param freeFunction address of a native function of type
* {@code void f(void* nativePtr)} used to free this
* kind of native allocation
* @param size estimated size in bytes of the part of the described
* native memory allocated with system malloc.
* Approximate values, including wild guesses, are acceptable.
* Unlike {@code createNonmalloced()}, this size is used
* only by heap analysis tools; garbage collector triggering
* instead looks directly at {@code mallinfo()} information.
* @return allocated {@link NativeAllocationRegistry}
* @throws IllegalArgumentException If {@code size} is negative
*
* @hide
*/
@SystemApi(client = MODULE_LIBRARIES)
public static NativeAllocationRegistry createMalloced(
@NonNull ClassLoader classLoader, long freeFunction, long size) {
return new NativeAllocationRegistry(classLoader, freeFunction, size, true);
}
/**
* Return a {@link NativeAllocationRegistry} for native memory that is mostly
* allocated by the system memory allocator. This version uses a default size,
* thus providing less information than desired for heap analysis tools.
* It should only be used when the native allocation is expected to be small,
* but there is no reasonable way to provide a meaningful size estimate.
* @param classLoader ClassLoader that was used to load the native
* library {@code freeFunction} belongs to.
* @param freeFunction address of a native function of type
* {@code void f(void* nativePtr)} used to free this
* kind of native allocation
* @return allocated {@link NativeAllocationRegistry}
*
* @hide
*/
@SystemApi(client = MODULE_LIBRARIES)
@libcore.api.IntraCoreApi
public static NativeAllocationRegistry createMalloced(
@NonNull ClassLoader classLoader, long freeFunction) {
return new NativeAllocationRegistry(classLoader, freeFunction, DEFAULT_SIZE, true);
}
/**
* Constructs a NativeAllocationRegistry for a particular kind of native
* allocation.
* <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>. This is used to help inform the garbage
* collector about the possible need for collection. Memory allocated with
* native malloc is implicitly included, and ideally should not be included in this
* argument.
* <p>
* @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. If mallocAllocation is false, then this
* should ideally exclude memory allocated by system
* malloc. However including it will simply double-count it,
* typically resulting in slightly increased GC frequency.
* If mallocAllocation is true, then this affects only the
* frequency with which we sample the malloc heap, and debugging
* tools. In this case a value of zero is commonly used to
* indicate an unknown non-huge size.
* @param mallocAllocation the native object is primarily allocated via malloc.
*/
private NativeAllocationRegistry(ClassLoader classLoader, long freeFunction, long size,
boolean mallocAllocation) {
if (size < 0) {
throw new IllegalArgumentException("Invalid native allocation size: " + size);
}
this.classLoader = classLoader;
this.freeFunction = freeFunction;
this.size = mallocAllocation ? (size | IS_MALLOCED) : (size & ~IS_MALLOCED);
}
/**
* Constructs a {@link NativeAllocationRegistry} for a particular kind of native
* allocation.
* <p>
* New code should use the preceding factory methods rather than calling this
* constructor directly.
* <p>
* The {@code size} should be an estimate of the total number of
* native bytes this kind of native allocation takes up excluding bytes allocated
* with system malloc. Different
* {@link NativeAllocationRegistry}s must be used to register native allocations
* with different estimated sizes, even if they use the same
* {@code freeFunction}. This is used to help inform the garbage
* collector about the possible need for collection. Memory allocated with
* native malloc is implicitly included, and ideally should not be included in this
* argument.
* <p>
* @param classLoader ClassLoader that was used to load the native
* library {@code 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, excluding memory allocated with system malloc.
* A value of 0 indicates that the memory was allocated mainly
* with malloc.
*
* @hide
*/
@SystemApi(client = MODULE_LIBRARIES)
public NativeAllocationRegistry(@NonNull ClassLoader classLoader, long freeFunction, long size) {
this(classLoader, freeFunction, size, size == 0);
}
/**
* Registers a new native allocation and associated Java object with the
* runtime.
* This {@link NativeAllocationRegistry}'s {@code freeFunction} will
* automatically be called with {@code nativePtr} as its sole
* argument when {@code referent} becomes unreachable. If you
* maintain copies of {@code nativePtr} outside
* {@code referent}, you must not access these after
* {@code referent} becomes unreachable, because they may be dangling
* pointers.
* <p>
* The returned Runnable can be used to free the native allocation before
* {@code 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.
* <p>
* WARNING: This unconditionally takes ownership, i.e. deallocation
* responsibility of nativePtr. nativePtr will be DEALLOCATED IMMEDIATELY
* if the registration attempt throws an exception (other than one reporting
* a programming error).
*
* @param referent Non-{@code null} java object to associate the native allocation with
* @param nativePtr Non-zero address of the native allocation
* @return runnable to explicitly free native allocation
* @throws IllegalArgumentException if either referent or nativePtr is {@code null}.
* @throws OutOfMemoryError if there is not enough space on the Java heap
* in which to register the allocation. In this
* case, {@code freeFunction} will be
* called with {@code nativePtr} as its
* argument before the {@link OutOfMemoryError} is
* thrown.
*
* @hide
*/
@SystemApi(client = MODULE_LIBRARIES)
@libcore.api.IntraCoreApi
public @NonNull Runnable registerNativeAllocation(@NonNull Object referent, long nativePtr) {
if (referent == null) {
throw new IllegalArgumentException("referent is null");
}
if (nativePtr == 0) {
throw new IllegalArgumentException("nativePtr is null");
}
CleanerThunk thunk;
CleanerRunner result;
try {
thunk = new CleanerThunk();
Cleaner cleaner = Cleaner.create(referent, thunk);
result = new CleanerRunner(cleaner);
registerNativeAllocation(this.size);
} catch (VirtualMachineError vme /* probably OutOfMemoryError */) {
applyFreeFunction(freeFunction, nativePtr);
throw vme;
} // Other exceptions are impossible.
// Enable the cleaner only after we can no longer throw anything, including OOME.
thunk.setNativePtr(nativePtr);
// Ensure that cleaner doesn't get invoked before we enable it.
Reference.reachabilityFence(referent);
return result;
}
private class CleanerThunk implements Runnable {
private long nativePtr;
public CleanerThunk() {
this.nativePtr = 0;
}
public void run() {
if (nativePtr != 0) {
applyFreeFunction(freeFunction, nativePtr);
registerNativeFree(size);
}
}
public void setNativePtr(long nativePtr) {
this.nativePtr = nativePtr;
}
// Only for error reporting.
@Override public String toString() {
return super.toString() + "(freeFunction = 0x" + Long.toHexString(freeFunction)
+ ", nativePtr = 0x" + Long.toHexString(nativePtr) + ", size = " + size + ")";
}
}
private static class CleanerRunner implements Runnable {
private final Cleaner cleaner;
public CleanerRunner(Cleaner cleaner) {
this.cleaner = cleaner;
}
public void run() {
cleaner.clean();
}
}
// Inform the garbage collector of the allocation. We do this differently for
// malloc-based allocations.
private static void registerNativeAllocation(long size) {
VMRuntime runtime = VMRuntime.getRuntime();
if ((size & IS_MALLOCED) != 0) {
final long notifyImmediateThreshold = 300000;
if (size >= notifyImmediateThreshold) {
runtime.notifyNativeAllocationsInternal();
} else {
runtime.notifyNativeAllocation();
}
} else {
runtime.registerNativeAllocation(size);
}
}
// Inform the garbage collector of deallocation, if appropriate.
private static void registerNativeFree(long size) {
if ((size & IS_MALLOCED) == 0) {
VMRuntime.getRuntime().registerNativeFree(size);
}
}
/**
* Calls {@code freeFunction}({@code nativePtr}).
* Provided as a convenience in the case where you wish to manually free a
* native allocation using a {@code freeFunction} without using a
* {@link NativeAllocationRegistry}.
*
* @param freeFunction address of a native function used to free this
* kind of native allocation
* @param nativePtr pointer to pass to freeing function
*
* @hide
*/
@SystemApi(client = MODULE_LIBRARIES)
public static native void applyFreeFunction(long freeFunction, long nativePtr);
}