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

先思考一个问题:假设要设计一个池,我们要考虑哪些事情?

  1. 池要实现的功能?怎么实现?
  2. 池的可配置性?

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



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

    • 根据请求的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> 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()流程执行完毕,总结一下过程:

    • 遍历所有使用的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 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)