您的位置:首页 > Web前端

fesco源码解析(一):pool详解

2016-03-17 00:12 671 查看
先思考一个问题:假设要设计一个池,我们要考虑哪些事情?

池要实现的功能?怎么实现?

池的可配置性?

带着这些问题,看看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类:

池通过map组织:

<size1, Bucket>


<size2, Bucket>


<size3, Bucket>


entry 是 buckect里面item的大小

有些池有一些固定数量量的bucket,有些不是

池提供两个主要的操作

get()
返回一个跟指定大小相同或者大于指定大小的值,如果不能则通过alloc方法分配空间。

release()
释放一个值到池里面

除此之外,池订阅了MemoryTrimmableRegistry内存消除策略,对低内存的事件负责。池中的一些百分比的值被释放,不再属于这个池

Logical size
:是value明确的占用的大小,比如,byte arrays是它的长度,对于bitmap是pixels的数量。Bucketed size:代表一系列不连续的Logical size,每个bucketed size会容纳一些列的logical sizes, 比如,对于byte arrays使用powers 2提供一些列的size

TODO

详细分析一下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<V> 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<V> 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 t
4000
o 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()
流程执行完毕,总结一下过程:

根据请求的size计算合适的bucket的size

根据计算出的bucket的size获取相对应的bucket

如果获取到的bucket不为空,且value不为空,重新计算bucket对应的bucketsize

根据计算出的bucketsize计算value真实占用的byte的大小

used引用计数增加,freed计数减少

如果没有找到对应的bucket,则尝试重新分配bucket,分配失败抛异常。

削减内存到软上限

分析一下
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()
流程执行完毕,总结一下过程:

根据value计算bucket的size

根据计算出的bucket的size获取相对应的bucket

判断是否可以被pool realease,如果可以则放入bucket的linkedlist,如果不可以直接free

分析一下
trimToNothing()
方法

void trimToNothing() {

final List<Bucket<V>> bucketsToTrim = new ArrayList<>(mBuckets.size());
final SparseIntArray inUseCounts = new SparseIntArray();
//遍历所有使用的bucket,加入集合
synchronized (this) {
for (int i = 0; i < mBuckets.size(); ++i) {
final Bucket<V> 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<V> 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()
流程执行完毕,总结一下过程:

遍历所有使用的bucket,加入集合bucketsToTrim

遍历bucketsToTrim集合里面所有的bucket,bucket调用pop,然后释放资源

分析一下
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<V> 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()
流程执行完毕,总结一下过程:

计算需要释放的字节

根据bucket的size从小到大循环遍历释放资源,直到需要释放的bytes小于等于0

看一下子类需要实现的方法:

getBucketedSize(int)

getBucketedSizeForValue(Object)

getSizeInBytes(int)

alloc(int)

free(Object)

最后总结一下:

pool
------------------------------------------------
| [10byte] |       | [30byte] |
| [5byte ] |       | [35byte] |  ......
| [20byte] |       | [40byte] |
------------------------------------------------
20size的bucket    40size的bucket
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android