/*
* 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 javax.annotation.concurrent.ThreadSafe;
import java.util.concurrent.Semaphore;
import com.facebook.common.internal.Preconditions;
import com.facebook.common.internal.Throwables;
import com.facebook.common.internal.VisibleForTesting;
import com.facebook.common.memory.MemoryTrimType;
import com.facebook.common.memory.MemoryTrimmable;
import com.facebook.common.memory.MemoryTrimmableRegistry;
import com.facebook.common.references.CloseableReference;
import com.facebook.common.references.OOMSoftReference;
import com.facebook.common.references.ResourceReleaser;
/**
* Maintains a shareable reference to a byte array.
*
* <p> When accessing the shared array proper synchronization is guaranteed.
* Under hood the get method acquires an exclusive lock, which is released
* whenever the returned CloseableReference is closed.
*
* <p> If the currently available byte array is too small for a request
* it is replaced with a bigger one.
*
* <p> This class will also release the byte array if it is unused and
* collecting it can prevent an OOM.
*/
@ThreadSafe
public class SharedByteArray implements MemoryTrimmable {
@VisibleForTesting
final int mMinByteArraySize;
@VisibleForTesting
final int mMaxByteArraySize;
/**
* The underlying byte array.
*
* <p> If we receive a memory trim notification, or the runtime runs pre-OOM gc
* it will be cleared to reduce memory pressure.
*/
@VisibleForTesting
final OOMSoftReference<byte[]> mByteArraySoftRef;
/**
* Synchronization primitive used by this implementation
*/
@VisibleForTesting
final Semaphore mSemaphore;
private final ResourceReleaser<byte[]> mResourceReleaser;
public SharedByteArray(
MemoryTrimmableRegistry memoryTrimmableRegistry,
PoolParams params) {
Preconditions.checkNotNull(memoryTrimmableRegistry);
Preconditions.checkArgument(params.minBucketSize > 0);
Preconditions.checkArgument(params.maxBucketSize >= params.minBucketSize);
mMaxByteArraySize = params.maxBucketSize;
mMinByteArraySize = params.minBucketSize;
mByteArraySoftRef = new OOMSoftReference<byte[]>();
mSemaphore = new Semaphore(1);
mResourceReleaser = new ResourceReleaser<byte[]>() {
@Override
public void release(byte[] unused) {
mSemaphore.release();
}
};
memoryTrimmableRegistry.registerMemoryTrimmable(this);
}
/**
* Get exclusive access to the byte array of size greater or equal to the passed one.
*
* <p> Under the hood this method acquires an exclusive lock that is released when
* the returned reference is closed.
*/
public CloseableReference<byte[]> get(int size) {
Preconditions.checkArgument(size > 0, "Size must be greater than zero");
Preconditions.checkArgument(size <= mMaxByteArraySize, "Requested size is too big");
mSemaphore.acquireUninterruptibly();
try {
byte[] byteArray = getByteArray(size);
return CloseableReference.of(byteArray, mResourceReleaser);
} catch (Throwable t) {
mSemaphore.release();
throw Throwables.propagate(t);
}
}
private byte[] getByteArray(int requestedSize) {
final int bucketedSize = getBucketedSize(requestedSize);
byte[] byteArray = mByteArraySoftRef.get();
if (byteArray == null || byteArray.length < bucketedSize) {
byteArray = allocateByteArray(bucketedSize);
}
return byteArray;
}
/**
* Responds to memory pressure by simply 'discarding' the local byte array if it is not used
* at the moment.
*
* @param trimType kind of trimming to perform (ignored)
*/
@Override
public void trim(MemoryTrimType trimType) {
if (!mSemaphore.tryAcquire()) {
return;
}
try {
mByteArraySoftRef.clear();
} finally {
mSemaphore.release();
}
}
@VisibleForTesting
int getBucketedSize(int size) {
size = Math.max(size, mMinByteArraySize);
return Integer.highestOneBit(size - 1) * 2;
}
private synchronized byte[] allocateByteArray(int size) {
// Start with clearing reference and releasing currently owned byte array
mByteArraySoftRef.clear();
byte[] byteArray = new byte[size];
mByteArraySoftRef.set(byteArray);
return byteArray;
}
}