/*
* 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.common.objectpool;
import java.lang.reflect.Array;
import com.facebook.common.logging.FLog;
import com.facebook.common.time.MonotonicClock;
/**
* Generic object pool implementation for arbitrary types. Provides an interface for performing
* actions when allocating from / releasing to the pool, as well as a simple compaction strategy
* based upon the last time we needed the pool to be as large as it is.
* @param <T> type of the object to pool
*/
public class ObjectPool<T> {
private static final Class<?> TAG = ObjectPool.class;
private final Class<T> mClazz;
// minimum and maximum size for the pool
private final int mMinSize;
private final int mMaxSize;
// increment size the pool size is increased by when there is not enough space
private final int mIncrementSize;
private final Allocator<T> mAllocator;
private final MonotonicClock mClock;
// amount of time in ms since the last 'low capacity' event before we reduce the size of the pool
private final long mCompactionDelayMs;
// The last time we had low supply (denoted as less than 2x the increment size in spare capacity)
private long mLastLowSupplyTimeMs;
private T[] mPool;
private int mSize;
/**
* Generic allocator interface for the pool. Encapsulates the per-object logic needed to
* instantiate, destroy, and do operations when an object is allocated from / returned to the pool
* The call sequence for a given object will look like:
* obj1 = Allocator.create()
* [
* Allocator.onAllocate(obj1)
* Allocator.onRelease(obj1)
* ] (zero or more times)
* Allocator.onDestroy(obj1) // if the object is freed from the pool
* @param <T>
*/
public interface Allocator<T> {
/**
* Create and return a new instance of T
* @return new instance of T suitable for pooling
*/
public T create();
/**
* Handler for when an instance of T is allocated from the pool
* @param obj instance of T that is being allocated
*/
public void onAllocate(T obj);
/**
* Handler for when an instance of T is returned to the pool
* @param obj instance of T that is being returned
*/
public void onRelease(T obj);
}
/**
* Basic implementation of an Allocator. Uses new to create the object instance
* @param <T>
*/
public static class BasicAllocator<T> implements Allocator<T> {
Class<T> mClazz;
public BasicAllocator(Class<T> clazz) {
mClazz = clazz;
}
@Override
public T create() {
try {
return mClazz.newInstance();
} catch (InstantiationException e) {
FLog.e(TAG, "Couldn't instantiate object", e);
} catch (IllegalAccessException e) {
FLog.e(TAG, "Couldn't instantiate object", e);
}
return null;
}
@Override
public void onAllocate(T obj) {
}
@Override
public void onRelease(T obj) {
}
}
@SuppressWarnings("unchecked")
public ObjectPool(Class<T> clazz, int minSize, int maxSize, int incrementSize,
long compactionDelay, Allocator<T> alloc, MonotonicClock clock) {
mClazz = clazz;
mMinSize = Math.max(minSize, 0);
mMaxSize = Math.max(mMinSize, maxSize);
mIncrementSize = Math.max(incrementSize, 1);
mCompactionDelayMs = compactionDelay;
mAllocator = alloc;
mClock = clock;
mPool = (T[]) Array.newInstance(mClazz, mMinSize);
}
/**
* Return a free instance of T for use. Tries to return an already allocated instance, or creates
* a new one if none exist
* @return an instance of T for use
*/
public synchronized T allocate() {
T obj;
if (mSize > 0) {
--mSize;
obj = mPool[mSize];
mPool[mSize] = null;
} else {
obj = mAllocator.create();
}
mAllocator.onAllocate(obj);
return obj;
}
/**
* Return a previously allocated object back to the pool
* @param obj object to return to the pool
*/
public synchronized void release(T obj) {
// Check usage at the beginning of release since it represents the low point
checkUsage();
mAllocator.onRelease(obj);
if (mSize < mMaxSize) {
if (mSize + 1 > mPool.length) {
resizePool(Math.min(mMaxSize, mPool.length + mIncrementSize));
}
mPool[mSize++] = obj;
}
}
/**
* Checks whether the compaction policies set for this pool have been satisfied
*/
public synchronized void checkUsage() {
final long now = mClock.now();
// this check prevents compaction from occurring by setting the last timestamp
// to right now when the size is less than 2x the increment size (default
// ObjectPoolBuilder.DEFAULT_INCREMENT_SIZE).
if (mSize < 2*mIncrementSize) {
mLastLowSupplyTimeMs = now;
}
if (now - mLastLowSupplyTimeMs > mCompactionDelayMs) {
FLog.d(TAG, "ObjectPool.checkUsage is compacting the pool.");
compactUsage();
}
}
/**
* Forcibly compacts the pool by the set increment size, regardless of the compaction policy.
*/
public synchronized void compactUsage() {
int newSize = Math.max(mPool.length - mIncrementSize, mMinSize);
if (newSize != mPool.length) {
resizePool(newSize);
}
}
/**
* Get the number of free objects currently in the pool.
* @return the number of free objects
*/
public int getPooledObjectCount() {
return mSize;
}
public int getMinimumSize() {
return mMinSize;
}
public int getMaximumSize() {
return mMaxSize;
}
public int getIncrementSize() {
return mIncrementSize;
}
public long getCompactionDelayMs() {
return mCompactionDelayMs;
}
/**
* Get the total size of the array backing the pool. This will always be >= getPooledObjectCount
* @return the pool size
*/
public int getPoolSize() {
return mPool.length;
}
@SuppressWarnings("unchecked")
private void resizePool(int newSize) {
T[] newArr = (T[]) Array.newInstance(mClazz, newSize);
System.arraycopy(mPool, 0, newArr, 0, Math.min(mPool.length, newSize));
mPool = newArr;
mSize = Math.min(mSize, newSize);
}
}