/* * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.facebook.imagepipeline.memory; import java.io.Closeable; import android.util.Log; import com.facebook.common.internal.Preconditions; import com.facebook.common.internal.VisibleForTesting; import com.facebook.common.internal.DoNotStrip; import com.facebook.common.soloader.SoLoaderShim; /** * Wrapper around chunk of native memory. * * <p> This class uses JNI to obtain pointer to native memory and read/write data from/to it. * * <p> Native code used by this class is shipped as part of libimagepipeline_memory.so * * @ThreadSafe */ @DoNotStrip public class NativeMemoryChunk implements Closeable { private static final String TAG = "NativeMemoryChunk"; static { SoLoaderShim.loadLibrary("gnustl_shared"); SoLoaderShim.loadLibrary("memchunk"); } /** * Address of memory chunk wrapped by this NativeMemoryChunk */ private final long mNativePtr; /** * size of the memory region */ private final int mSize; /** * flag indicating if this object was closed * @GuardedBy("this") */ private boolean mClosed; public NativeMemoryChunk(final int size) { Preconditions.checkArgument(size > 0); mSize = size; mNativePtr = nativeAllocate(mSize); mClosed = false; } @VisibleForTesting public NativeMemoryChunk() { mSize = 0; mNativePtr = 0; mClosed = true; } /** * This has to be called before we get rid of this object in order to release underlying memory */ public synchronized void close() { if (!mClosed) { mClosed = true; nativeFree(mNativePtr); } } /** * Is this chunk already closed (aka freed) ? * @return true, if this chunk has already been closed */ public synchronized boolean isClosed() { return mClosed; } /** * Get the size of this memory chunk. * Ignores if this chunk has been closed */ public int getSize() { return mSize; } /** * Copy bytes from byte array to native memory. * @param nativeMemoryOffset number of first byte to be written by copy operation * @param byteArray byte array to copy from * @param byteArrayOffset number of first byte in byteArray to copy * @param count number of bytes to copy * @return number of bytes written */ public synchronized int write( int nativeMemoryOffset, final byte[] byteArray, int byteArrayOffset, int count) { Preconditions.checkNotNull(byteArray); Preconditions.checkState(!isClosed()); final int actualCount = adjustByteCount(nativeMemoryOffset, count); checkBounds(nativeMemoryOffset, byteArray.length, byteArrayOffset, actualCount); nativeCopyFromByteArray( mNativePtr + nativeMemoryOffset, byteArray, byteArrayOffset, actualCount); return actualCount; } /** * Copy bytes from native memory to byte array. * @param nativeMemoryOffset number of first byte to copy * @param byteArray byte array to copy to * @param byteArrayOffset number of first byte in byte array to be written * @param count number of bytes to copy * @return number of bytes read */ public synchronized int read( final int nativeMemoryOffset, final byte[] byteArray, final int byteArrayOffset, final int count) { Preconditions.checkNotNull(byteArray); Preconditions.checkState(!isClosed()); final int actualCount = adjustByteCount(nativeMemoryOffset, count); checkBounds(nativeMemoryOffset, byteArray.length, byteArrayOffset, actualCount); nativeCopyToByteArray(mNativePtr + nativeMemoryOffset, byteArray, byteArrayOffset, actualCount); return actualCount; } /** * Read byte at given offset. * @param offset * @return byte at given offset */ public synchronized byte read(int offset) { Preconditions.checkState(!isClosed()); Preconditions.checkArgument(offset >= 0); Preconditions.checkArgument(offset < mSize); return nativeReadByte(mNativePtr + offset); } /** * Copy bytes from native memory wrapped by this NativeMemoryChunk instance to * native memory wrapped by other NativeMemoryChunk * @param offset number of first byte to copy * @param other other NativeMemoryChunk to copy to * @param otherOffset number of first byte to write to * @param count number of bytes to copy */ public void copy( final int offset, final NativeMemoryChunk other, final int otherOffset, final int count) { Preconditions.checkNotNull(other); // This implementation acquires locks on this and other objects and then delegates to // doCopy which does actual copy. In order to avoid deadlocks we have to establish some linear // order on all NativeMemoryChunks and acquire locks according to this order. Fortunately // we can use mNativePtr for that purpose. So we have to address 3 cases: // Case 1: other memory chunk == this memory chunk if (other.mNativePtr == mNativePtr) { // we do not allow copying to the same address // lets log warning and not copy Log.w( TAG, "Copying from NativeMemoryChunk " + Integer.toHexString(System.identityHashCode(this)) + " to NativeMemoryChunk " + Integer.toHexString(System.identityHashCode(other)) + " which share the same address " + Long.toHexString(mNativePtr)); Preconditions.checkArgument(false); } // Case 2: other memory chunk < this memory chunk if (other.mNativePtr < mNativePtr) { synchronized (other) { synchronized (this) { doCopy(offset, other, otherOffset, count); } } return; } // Case 3: other memory chunk > this memory chunk synchronized (this) { synchronized (other) { doCopy(offset, other, otherOffset, count); } } } public long getNativePtr() { return mNativePtr; } /** * This does actual copy. It should be called only when we hold locks on both this and * other objects */ private void doCopy( final int offset, final NativeMemoryChunk other, final int otherOffset, final int count) { Preconditions.checkState(!isClosed()); Preconditions.checkState(!other.isClosed()); checkBounds(offset, other.mSize, otherOffset, count); nativeMemcpy(other.mNativePtr + otherOffset, mNativePtr + offset, count); } /** * A finalizer, just in case. Just delegates to {@link #close()} * @throws Throwable */ @Override protected void finalize() throws Throwable { if (isClosed()) { return; } Log.w( TAG, "finalize: Chunk " + Integer.toHexString(System.identityHashCode(this)) + " still active. Underlying address = " + Long.toHexString(mNativePtr)); // do the actual clearing try { close(); } finally { super.finalize(); } } /** * Computes number of bytes that can be safely read/written starting at given offset, but no more * than count. */ private int adjustByteCount(final int offset, final int count) { final int available = Math.max(0, mSize - offset); return Math.min(available, count); } /** * Check that copy/read/write operation won't access memory it should not */ private void checkBounds( final int myOffset, final int otherLength, final int otherOffset, final int count) { Preconditions.checkArgument(count >= 0); Preconditions.checkArgument(myOffset >= 0); Preconditions.checkArgument(otherOffset >= 0); Preconditions.checkArgument(myOffset + count <= mSize); Preconditions.checkArgument(otherOffset + count <= otherLength); } /** * Delegate to one of native memory allocation function */ @DoNotStrip private static native long nativeAllocate(int size); /** * Delegate to appropriate memory releasing function */ @DoNotStrip private static native void nativeFree(long address); /** * Copy count bytes pointed by mNativePtr to array, starting at position offset */ @DoNotStrip private static native void nativeCopyToByteArray( long address, byte[] array, int offset, int count); /** * Copy count bytes from byte array to native memory pointed by mNativePtr. */ @DoNotStrip private static native void nativeCopyFromByteArray( long address, byte[] array, int offset, int count); /** * Copy count bytes from memory pointed by fromPtr to memory pointed by toPtr */ @DoNotStrip private static native void nativeMemcpy( long toPtr, long fromPtr, int count); /** * Read single byte from given address * @param fromPtr address to read byte from */ @DoNotStrip private static native byte nativeReadByte(long fromPtr); }