Volley中的PoolingByteArrayOutputStream
在启动Dalvik虚拟机的时候,我们可以分别通过-Xms、-Xmx和-XX:HeapGrowthLimit三个选项来指定上述三个值,以上三个值分别表示表示:
Starting Size: Dalvik虚拟机启动的时候,会先分配一块初始的堆内存给虚拟机使用。
Growth Limit: 是系统给每一个程序的最大堆上限,超过这个上限,程序就会OOM。
Maximum Size: 不受控情况下的最大堆内存大小,起始就是我们在用largeheap属性的时候,可以从系统获取的最大堆大小。
同时除了上面的这个三个指标外,还有几个指标也是值得我们关注的,那就是堆最小空闲值
(Min Free)、堆最大空闲值(Max Free)和堆目标利用率(Target Utilization)。
假设在某一次GC之后,存活对象占用内存的大小为LiveSize,那么这时候堆的理想大小应该
为(LiveSize / U)。但是(LiveSize / U)必须大于等于(LiveSize + MinFree)并且
小于等于(LiveSize + MaxFree),每次GC后垃圾回收器都会尽量让堆的利用率往目标利用
率靠拢。所以当我们尝试手动去生成一些几百K的对象,试图去扩大可用堆大小的时候,反而
会导致频繁的GC,因为这些对象的分配会导致GC,而GC后会让堆内存回到合适的比例,而我
们使用的局部变量很快会被回收理论上存活对象还是那么多,我们的堆大小也会缩减回来无法
达到扩充的目的。 与此同时这也是产生CONCURRENT GC的一个因素,因为手动生成的对象可
能会使堆内存的使用达到触发CONCURRENT GC的阈值。
上述由堆目标利用率导致的堆容量重置可用PoolingByteArrayOutputStream来避免,ByteArrayPool维护的byte数组缓存池减少内存分配的操作,自然也就减少GC的发生。
PoolingByteArrayOutputStream借助一个ByteArrayPool类来维护byte数组缓存池,因为频繁创建、清除byte数组可能引起严重的内存抖动。
下面是分析,精华都在注释里!
ByteArrayPool主要代码:
public class ByteArrayPool {
//全部byte数组的占用空间限制
private final int mSizeLimit;
//mBuffersByLastUse维护缓存byte数组的使用顺序,在压缩缓存池的时候将删掉最后的byte数组
private List<byte[]> mBuffersByLastUse = new LinkedList<byte[]>();
//mBuffersBySize维护缓存byte数组的大小顺序,PoolingByteArrayOutputStream按需提取byte数组
private List<byte[]> mBuffersBySize = new ArrayList<byte[]>(64);
public synchronized byte[] getBuf(int len) {
for (int i = 0; i < mBuffersBySize.size(); i++) {
byte[] buf = mBuffersBySize.get(i);
if (buf.length >= len) {
mCurrentSize -= buf.length;
mBuffersBySize.remove(i);
mBuffersByLastUse.remove(buf);
return buf;
}
}
//可以返回大于mSizeLimit的byte数组,后续不会进入mBuffers
return new byte[len];
}
/**
* Returns a buffer to the pool, throwing away old buffers if the pool would exceed its allotted
* size.
*
* @param buf the buffer to return to the pool.
*/
public synchronized void returnBuf(byte[] buf) {
if (buf == null || buf.length > mSizeLimit) {
return;
}
mBuffersByLastUse.add(buf);
//二分查找
int pos = Collections.binarySearch(mBuffersBySize, buf, BUF_COMPARATOR);
if (pos < 0) {
pos = -pos - 1;
}
mBuffersBySize.add(pos, buf);
mCurrentSize += buf.length;
trim();
}
/**
* Removes buffers from the pool until it is under its size limit.
*/
private synchronized void trim() {
while (mCurrentSize > mSizeLimit) {
byte[] buf = mBuffersByLastUse.remove(0);
mBuffersBySize.remove(buf);
mCurrentSize -= buf.length;
}
}
PoolingByteArrayOutputStream主要代码:
public class PoolingByteArrayOutputStream extends ByteArrayOutputStream {
public PoolingByteArrayOutputStream(ByteArrayPool pool, int size) {
mPool = pool;
buf = mPool.getBuf(Math.max(size, DEFAULT_SIZE));
}
private void expand(int i) {
/* Can the buffer handle @i more bytes, if not expand it */
if (count + i <= buf.length) {
return;
}
byte[] newbuf = mPool.getBuf((count + i) * 2);
System.arraycopy(buf, 0, newbuf, 0, count);
mPool.returnBuf(buf);
buf = newbuf;
}
@Override
public synchronized void write(byte[] buffer, int offset, int len) {
//取代父类ByteArrayOutputStream的grow方法实现扩大缓存buf
//(没有像grow方法那样针对OutOfMemory做处理,虽然一般不会出现)
expand(len);
super.write(buffer, offset, len);
}
@Override
public synchronized void write(int oneByte) {
expand(1);
super.write(oneByte);
}
}