Netty源码分析:PoolChunkList
Posted HelloWorld_EE
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Netty源码分析:PoolChunkList相关的知识,希望对你有一定的参考价值。
Netty源码分析:PoolChunkList
在博文 Netty源码分析:PoolArena中我们知道,在第一次申请内存时,会调用如下的allocateNormal方法来新建一个Chunk,然后在此Chunk上分配内存。分配完成之后会将这个Chunk添加到名为qInit的PoolChunkList中。在下次分配内存时会先尝试在6个PoolChunkList来寻找chunk来分配内存。
private synchronized void allocateNormal(PooledByteBuf<T> buf, int reqCapacity, int normCapacity)
if (q050.allocate(buf, reqCapacity, normCapacity) || q025.allocate(buf, reqCapacity, normCapacity) ||
q000.allocate(buf, reqCapacity, normCapacity) || qInit.allocate(buf, reqCapacity, normCapacity) ||
q075.allocate(buf, reqCapacity, normCapacity) || q100.allocate(buf, reqCapacity, normCapacity))
return;
// Add a new chunk.
PoolChunk<T> c = newChunk(pageSize, maxOrder, pageShifts, chunkSize);
long handle = c.allocate(normCapacity);
assert handle > 0;
c.initBuf(buf, handle, reqCapacity);
qInit.add(c);
本博文主要介绍下PoolChunkList这个类。
1、属性和构造函数
在PoolArean这个类实例化6个PoolChunkList的代码如下:
q100 = new PoolChunkList<T>(this, null, 100, Integer.MAX_VALUE);
q075 = new PoolChunkList<T>(this, q100, 75, 100);
q050 = new PoolChunkList<T>(this, q075, 50, 100);
q025 = new PoolChunkList<T>(this, q050, 25, 75);
q000 = new PoolChunkList<T>(this, q025, 1, 50);
qInit = new PoolChunkList<T>(this, q000, Integer.MIN_VALUE, 25);
q100.prevList = q075;
q075.prevList = q050;
q050.prevList = q025;
q025.prevList = q000;
q000.prevList = null;
qInit.prevList = qInit;
借此机会就先从PoolChunkList这个类的构造函数开始介绍。PoolChunkList这个类的字段和构造函数如下:
final class PoolChunkList<T>
private final PoolArena<T> arena; //表示这个chunkList所那个所持有
//构成链表
private final PoolChunkList<T> nextList;
PoolChunkList<T> prevList;
private final int minUsage;//最小使用量
private final int maxUsage;//最大使用量
private PoolChunk<T> head;//保存的是由chunk构成的链表的头节点。
// TODO: Test if adding padding helps under contention
//private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7;
PoolChunkList(PoolArena<T> arena, PoolChunkList<T> nextList, int minUsage, int maxUsage)
this.arena = arena;
this.nextList = nextList;
this.minUsage = minUsage;
this.maxUsage = maxUsage;
字段和构造都比较好理解哈,从中知道如下两点即可:
1)每个PoolChunkList中用head字段维护一个PoolChunk链表的头部
2)PoolChunkList自己也将构成一个链表,例如:在PoolArena实例中就存在如下的6个PoolChunkList,且这6个PoolChunkList构成的链表为:qInit->q000<->q025<->q050<->q075<->q100,且qInit.prevList = qInit
,具体如下图所示。
qInit:存储已使用内存在0-25%的chunk,即保存剩余内存在75%~100%的chunk
q000:存储已使用内存在1-50%的chunk
q025:存储已使用内存在25-75%的chunk
q050:存储已使用内存在50-100%个chunk
q075:存储已使用内存在75-100%个chunk
q100:存储已使用内存在100%chunk
为什么链表是这样的顺序排列的?
原因在于:随着chunk中page的不断分配和释放,会导致很多碎片内存段,大大增加了之后分配一段连续内存的失败率,针对这种情况,可以把内存使用量较大的chunk放到PoolChunkList链表更后面,这样就便于内存的成功分配。
从PoolArena类中如下的allocateNormal中也可以反应出这一点:首先是在q050这个内存使用量在[50,100]的PoolChunkList中来尝试分配,如果分配失败,可能原因是需要分配的内存过大,则然后就会依次尝试在内存使用量较小的PoolChunkList来完成内存分配。
private synchronized void allocateNormal(PooledByteBuf<T> buf, int reqCapacity, int normCapacity)
if (q050.allocate(buf, reqCapacity, normCapacity) || q025.allocate(buf, reqCapacity, normCapacity) ||
q000.allocate(buf, reqCapacity, normCapacity) || qInit.allocate(buf, reqCapacity, normCapacity) ||
q075.allocate(buf, reqCapacity, normCapacity) || q100.allocate(buf, reqCapacity, normCapacity))
return;
// Add a new chunk.
PoolChunk<T> c = newChunk(pageSize, maxOrder, pageShifts, chunkSize);
long handle = c.allocate(normCapacity);
assert handle > 0;
c.initBuf(buf, handle, reqCapacity);
qInit.add(c);
2、add(PoolChunk chunk)
下面来看这个add函数,具体代码如下:
void add(PoolChunk<T> chunk)
if (chunk.usage() >= maxUsage)
nextList.add(chunk);
return;
chunk.parent = this;
if (head == null)
head = chunk;
chunk.prev = null;
chunk.next = null;
else
chunk.prev = null;
chunk.next = head;
head.prev = chunk;
head = chunk;
函数功能为:添加一个Chunk节点在这个PoolChunkList的Chunk链表的头部。
注意:如果这个chunk的内存使用量大于该PoolChunkList的最大使用量,则添加到该PoolChunkList的下一个PoolChunkList中去。这里就保证了每个PoolChunkList中都是存储的是其[minUsage,maxUsage]范围内的chunk。
3、allocate
下面来看下怎么来利用PoolChunkList来进行内存分配的。
boolean allocate(PooledByteBuf<T> buf, int reqCapacity, int normCapacity)
if (head == null) //根据head是否为null来判断此PoolChunkList没有chunk可用,如果没有,则返回false。
return false;
for (PoolChunk<T> cur = head;;)
//利用chunk来进行内存分配,具体逻辑可以见相应的博文
long handle = cur.allocate(normCapacity);
if (handle < 0)
cur = cur.next;
if (cur == null)
return false;
else
cur.initBuf(buf, handle, reqCapacity);
if (cur.usage() >= maxUsage)
remove(cur);
nextList.add(cur);
return true;
该函数也比较好理解哈,首先检查head是否为null,如果为null则说明此PoolChunkList没有chunk可用,如果不为null则从head节点开始分配内存并初始化到buf上。
要注意的一点是:在分配内存之后,会对该chunk的使用量进行判断,如果使用量大于maxUsage,则将此chunk从该PoolChunkList中移除然后将此chunk添加到后一个PoolChunkList中。
当然,chunk除了内存被分配,也会被释放,被释放之后,使用量减少,即内存剩余量增大,如果该chunk的使用量小于该PoolChunkList的minUsage,则就可能会被添加到前一个PoolChunkList中。
从下面的代码中注意一点特殊情况:如果没有下一个PoolChunkList,则说明该chunk的使用量为0,Arena就将此chunk销毁。
void free(PoolChunk<T> chunk, long handle)
chunk.free(handle);
if (chunk.usage() < minUsage)
remove(chunk);
if (prevList == null)
assert chunk.usage() == 0;
arena.destroyChunk(chunk);
else
prevList.add(chunk);
就是因为存在内存的分配和释放,而会导致chunk在不同的PoolChunkList中移动,但是为了防止移动的太过于频繁,则采取的措施是:每个chunkList的都有一个上下限:minUsage和maxUsage,两个相邻的chunkList,前一个的maxUsage和后一个的minUsage必须有一段交叉值进行缓冲。以下为PoolArena中包含的6个PoolChunkList的minUsage和maxUsage。
qInit:存储已使用内存在0-25%的chunk,即保存剩余内存在75%~100%的chunk
q000:存储已使用内存在1-50%的chunk
q025:存储已使用内存在25-75%的chunk
q050:存储已使用内存在50-100%个chunk
q075:存储已使用内存在75-100%个chunk
q100:存储已使用内存在100%chunk
小结
比较简单哈。看完我们需要记住如下的两点:
1、 每个PoolChunkList中用head字段维护一个PoolChunk链表的头部
2、当需要进行内存分配时,会依次遍历该PoolChunkList中的PoolChunk节点来完成,完成之后,会判断此PoolChunk的内存使用量是否大于该PoolChunkLis他的maxUsage,如果大于,则将此chunk放到下一个PoolChunkList中。
3、当chunk由于内存释放的原因而导致内存使用量减少,即剩余内存量增大,如果小于此PoolChunkList的minUsage,则将其加入到上一个PoolChunkList中去。
以上是关于Netty源码分析:PoolChunkList的主要内容,如果未能解决你的问题,请参考以下文章