先思考一个问题:假设要设计一个池,我们要考虑哪些事情?
带着这些问题,看看fesco的源码。
看一下fesco里面pool的继承结构
pool
|--BasePool
|
--BitmapPool
|
--GenericByteArrayPool
|
...
第一个问题:要实现的功能Pool类
V get(int size); //从池里面获取资源
void release(V value);//把不用的资源放回池
第二个问题:配置参数PoolParams类
public final int maxSizeHardCap;//硬上线
public final int maxSizeSoftCap;//软上线
public final SparseIntArray bucketSizes;// key bucketSize value maxLength
public final int minBucketSize;
public final int maxBucketSize;
public final int maxNumThreads;
看一下这几个参数的意义:
maxSizeHardCap:最大软上限,当池达到这个限制,池尝试释放空间,直到池的size小于软上限,或者剩余的空间为0
maxSizeSoftCap:最大硬上限,当池的大小超过硬上线,分配空间失败,并抛出PoolSizeViolationException异常
minBucketSize:最小的bucket大小,代表吃中的bucket最小size,用来保证所有的bucket持有的元素的size小于等于这个值。
maxBucketSize:最大的bucket大小,代表池中的bucket的最大的size,用来约束所有的bucket只允许元素小于或者等于这个值,如果超过这个值,会抛出异常。
maxNumThreads:最大可以访问这个池的线程的数量
bucketSizes:bucket数量,池配置了一系列固定大小的bucket。bucketSizes代表要创建各个bucket的size。此外,每个bucket规定了一个maxLength,用来限制bucket中的已经使用和未使用的元素的数量的总和。maxLength是个软上限,不会导致异常。它简单的用来控制释放的路径。当bucket大小是预先指定的,仍然可以池请求非标准尺寸,这种情况简单的当做 alloc/free 请求处理,并且值不会被池所持有。如果这个参数为空,pool将按需创建bucket
看一下Bucket是什么?
好吧,翻译一下doc
Bucket是BasePool的构成成分。池通过一系列的bucket持有它的空闲的值,每个bucket代表一系列的相同的size的值。每个bucket持有一系列的空闲的值。当池收到一个指定大小的get()请求的时候,池找到合适的bucket,返回给池的get()。当bucket空闲的列表没有空闲的时候,从空闲列表里面返回一个entry,并从空闲列表里面移除。类似的当一个value通过调用release释放到池里面,池定位合适的bucket,并且把value返回给bucket的空闲列表。bucket持有当前正在使用的item的数量。换句话说,就是bucket中的values现在被调用者使用着,但是不在空闲列表里面。bucket中的变量:length = mInUseCount + freelist size。maxLength变量代表这个bucket能增长到的最大值,这个值被池用来决定是否要释放values到bucket。
Bucket类:
通过LinkedList
实现空闲列表
final Queue mFreeList;
mFreeList = new LinkedList();//
get()
通过调用pop()
,并把使用计数mInUseLength++
release()
,只是简单的mFreeList.add(value);
BasePool类:
池提供两个主要的操作
get()
返回一个跟指定大小相同或者大于指定大小的值,如果不能则通过alloc方法分配空间。
release()
释放一个值到池里面
除此之外,池订阅了MemoryTrimmableRegistry内存消除策略,对低内存的事件负责。池中的一些百分比的值被释放,不再属于这个池
Logical size
:是value明确的占用的大小,比如,byte arrays是它的长度,对于bitmap是pixels的数量。Bucketed size:代表一系列不连续的Logical size,每个bucketed size会容纳一些列的logical sizes, 比如,对于byte arrays使用powers 2提供一些列的size
详细分析一下pool流程
初始化buckets
// initialize the buckets
mBuckets = new SparseArray
分析一下get()
方法
public V get(int size) {
ensurePoolSizeInvariant();
//1.根据请求的size,获取适合请求的bucket的size
int bucketedSize = getBucketedSize(size);
int sizeInBytes = -1;
synchronized (this) {
//2.根据指定的bucketSize获取一个bucket,如果bucket不存在,则重新创建一个
Bucket bucket = getBucket(bucketedSize);
if (bucket != null) {
// find an existing value that we can reuse
V value = bucket.get();
if (value != null) {
Preconditions.checkState(mInUseValues.add(value));
// It is possible that we got a 'larger' value than we asked for.
// lets recompute size in bytes here
//有可能获取到的value的size大于请求的,所以重新计算size
bucketedSize = getBucketedSizeForValue(value);
//计算获取到的bucket真实占用的字节的大小
sizeInBytes = getSizeInBytes(bucketedSize);
//使用的byte和计数+1
mUsed.increment(sizeInBytes);//使用计数器增加
//空闲的减少
mFree.decrement(sizeInBytes);//空闲计数器减少
....
return value;
}
// fall through
}
//如果没有找到对应的bucket,则需要重新计算是否可以分配新的bucket
// check to see if we can allocate a value of the given size without exceeding the hard cap
sizeInBytes = getSizeInBytes(bucketedSize);
//判断是否可以分配bucket,如果不能分配则直接抛异常
if (!canAllocate(sizeInBytes)) {
throw new PoolSizeViolationException(
mPoolParams.maxSizeHardCap,
mUsed.mNumBytes,
mFree.mNumBytes,
sizeInBytes);
}
//乐观的认为分配bucket成功了
// Optimistically assume that allocation succeeds - if it fails, we need to undo those changes
//引用计数增加
mUsed.increment(sizeInBytes);
if (bucket != null) {
bucket.incrementInUseCount();
}
}
V value = null;
try {
//在同步块外面分配值,因为他可能是重量级的,会阻塞调用者
// allocate the value outside the synchronized block, because it can be pretty expensive
// we could have done the allocation inside the synchronized block,
// but that would have blocked out other operations on the pool
//我们也可以在同步块内部分配,但是这样可能回阻塞在池上的操作
value = alloc(bucketedSize);
} catch (Throwable e) {
// Assumption we made previously is not valid - allocation failed. We need to fix internal counters.
//分配失败,引用计数回滚
synchronized (this) {
mUsed.decrement(sizeInBytes);
Bucket bucket = getBucket(bucketedSize);
if (bucket != null) {
bucket.decrementInUseCount();
}
}
Throwables.propagateIfPossible(e);
}
//注意:前面检查过是否超过了硬上限,然后接着调用了alloc,现在需要更新状态,有可能并发线程执行了相同的操作,
//导致超过了硬上限
// NOTE: We checked for hard caps earlier, and then did the alloc above. Now we need to
// update state - but it is possible that a concurrent thread did a similar operation - with
// the result being that we're now over the hard cap.
//这里忍受这种情况,因为项目的trim 调用应该会使内存使用回到正常的状态
// We are willing to live with that situation - especially since the trim call below should
// be able to trim back memory usage.
synchronized (this) {
Preconditions.checkState(mInUseValues.add(value));
// If we're over the pool's max size, try to trim the pool appropriately
trimToSoftCap();
... }
}
return value;
}
get()
流程执行完毕,总结一下过程:
分析一下release()
方法
public void release(V value) {
Preconditions.checkNotNull(value);
//根据value获取bucket对应的size
final int bucketedSize = getBucketedSizeForValue(value);
//获取占用的字节的大小
final int sizeInBytes = getSizeInBytes(bucketedSize);
synchronized (this) {
//根据bucketsize获取bucket
final Bucket bucket = getBucket(bucketedSize);
//如果value不在pool池里面引用,则直接释放
if (!mInUseValues.remove(value)) {
// This value was not ‘known’ to the pool (i.e.) allocated via the pool.
// Something is going wrong, so let’s free the value and report soft error.
…
//释放占用的资源
free(value);
mPoolStatsTracker.onFree(sizeInBytes);
} else {
//下面这几种情况直接释放资源,而不被pool回收
// free the value, if
// - 池超过了 maxSize
// - 没有bucket对应这个value
// - 有bucket对应这个value,但是bucket达到了maxLength
// - value不可以重用
if (bucket == null ||
bucket.isMaxLengthExceeded() ||
isMaxSizeSoftCapExceeded() ||
!isReusable(value)) {
if (bucket != null) {
bucket.decrementInUseCount();
}
...
free(value);
mUsed.decrement(sizeInBytes);
mPoolStatsTracker.onFree(sizeInBytes);
}
//
else {
//放入bucket linkedlist
bucket.release(value);
//计数响应的增加或者减少
mFree.increment(sizeInBytes);
mUsed.decrement(sizeInBytes);
... }
}
}
logStats();
}
}
release()
流程执行完毕,总结一下过程:
分析一下trimToNothing()
方法
void trimToNothing() {
final List> bucketsToTrim = new ArrayList<>(mBuckets.size());
final SparseIntArray inUseCounts = new SparseIntArray();
//遍历所有使用的bucket,加入集合
synchronized (this) {
for (int i = 0; i < mBuckets.size(); ++i) {
final Bucket bucket = mBuckets.valueAt(i);
if (bucket.getFreeListSize() > 0) {
bucketsToTrim.add(bucket);
}
inUseCounts.put(mBuckets.keyAt(i), bucket.getInUseCount());
}
// reinitialize the buckets
initBuckets(inUseCounts);
// free up the stats
mFree.reset();
logStats();
}
// the pool parameters 'may' have changed.
onParamsChanged();
//遍历集合里面所有的bucket,bucket调用pop,然后释放资源
// Explicitly free all the values.
// All the core data structures have now been reset. We no longer need to block other calls.
// This is true even for a concurrent trim() call
for (int i = 0; i < bucketsToTrim.size(); ++i) {
final Bucket bucket = bucketsToTrim.get(i);
while (true) {
// what happens if we run into an exception during the recycle. I'm going to ignore
// these exceptions for now, and let the GC handle the rest of the to-be-recycled-bitmaps
// in its usual fashion
V item = bucket.pop();
if (item == null) {
break;
}
free(item);
...
trimToNothing()
流程执行完毕,总结一下过程:
分析一下trimToSize()
方法
synchronized void trimToSize(int targetSize) {
// find how much we need to free
//计算需要释放的字节
int bytesToFree = Math.min(mUsed.mNumBytes + mFree.mNumBytes - targetSize, mFree.mNumBytes);
if (bytesToFree <= 0) {
return;
}
...
// now walk through the buckets from the smallest to the largest. Keep freeing things
// until we've gotten to what we want
//根据bucket的size从小到大循环遍历释放资源,直到需要释放的bytes小于等于0
for (int i = 0; i < mBuckets.size(); ++i) {
if (bytesToFree <= 0) {
break;
}
Bucket bucket = mBuckets.valueAt(i);
while (bytesToFree > 0) {
V value = bucket.pop();
if (value == null) {
break;
}
free(value);
bytesToFree -= bucket.mItemSize;
mFree.decrement(bucket.mItemSize);
}
}
...
trimToSize()
流程执行完毕,总结一下过程:
看一下子类需要实现的方法:
最后总结一下:
pool
------------------------------------------------
| [10byte] | | [30byte] |
| [5byte ] | | [35byte] | ......
| [20byte] | | [40byte] |
------------------------------------------------
20size的bucket 40size的bucket