fesco源码解析(一):pool详解
2016-03-17 00:12
671 查看
先思考一个问题:假设要设计一个池,我们要考虑哪些事情?
池要实现的功能?怎么实现?
池的可配置性?
带着这些问题,看看fesco的源码。
看一下fesco里面pool的继承结构
第一个问题:要实现的功能Pool类
第二个问题:配置参数PoolParams类
看一下这几个参数的意义:
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类:
通过
BasePool类:
池通过map组织:
entry 是 buckect里面item的大小
有些池有一些固定数量量的bucket,有些不是
池提供两个主要的操作
除此之外,池订阅了MemoryTrimmableRegistry内存消除策略,对低内存的事件负责。池中的一些百分比的值被释放,不再属于这个池
TODO
详细分析一下pool流程
初始化buckets
mBuckets = new SparseArray
分析一下
根据请求的size计算合适的bucket的size
根据计算出的bucket的size获取相对应的bucket
如果获取到的bucket不为空,且value不为空,重新计算bucket对应的bucketsize
根据计算出的bucketsize计算value真实占用的byte的大小
used引用计数增加,freed计数减少
如果没有找到对应的bucket,则尝试重新分配bucket,分配失败抛异常。
削减内存到软上限
分析一下
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不可以重用
根据value计算bucket的size
根据计算出的bucket的size获取相对应的bucket
判断是否可以被pool realease,如果可以则放入bucket的linkedlist,如果不可以直接free
分析一下
遍历所有使用的bucket,加入集合bucketsToTrim
遍历bucketsToTrim集合里面所有的bucket,bucket调用pop,然后释放资源
分析一下
计算需要释放的字节
根据bucket的size从小到大循环遍历释放资源,直到需要释放的bytes小于等于0
看一下子类需要实现的方法:
getBucketedSize(int)
getBucketedSizeForValue(Object)
getSizeInBytes(int)
alloc(int)
free(Object)
最后总结一下:
池要实现的功能?怎么实现?
池的可配置性?
带着这些问题,看看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
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories