在PoolThreadCache里面,分两种区域,cache和arena。前面我们讲解了cache,也就是之前存在过,我们直接在cache里面获取或者分配。但是第一次的时候,cache肯定是没有的,所以就要使用到内存区域arena。
我们来看源码:
final class PoolThreadCache {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(PoolThreadCache.class);
final PoolArena<byte[]> heapArena;
final PoolArena<ByteBuffer> directArena;
// Hold the caches for the different size classes, which are tiny, small and normal.
private final MemoryRegionCache<byte[]>[] tinySubPageHeapCaches;
private final MemoryRegionCache<byte[]>[] smallSubPageHeapCaches;
private final MemoryRegionCache<ByteBuffer>[] tinySubPageDirectCaches;
private final MemoryRegionCache<ByteBuffer>[] smallSubPageDirectCaches;
private final MemoryRegionCache<byte[]>[] normalHeapCaches;
private final MemoryRegionCache<ByteBuffer>[] normalDirectCaches;
}
前面我们讲解了MemoryRegionCache六个cache数组,现在我们讲解heapArena和directArena。
首先我们看arena的数据结构:
我们看到一个Arena由多个ChunkList组成,每个ChunkList之间通过双向链表的方式连接。一个ChunkList又由多个Chunk通过双向链表的方式组成。也就是各个Chunlist组成双向链表,ChunkList里面的Chunk又各自组成双向链表。
ChunList之间为什么要通过双向链表的方式连接?先给出答案:每个ChunkList里面的Chunk的使用率是不同的,Chunk使用率相同的归位一个ChunList,这样netty就能通过一定的算法找到合适的Chunk,提高内存分配的效率。
先看PoolArena里面的ChunkList的源码:
abstract class PoolArena<T> implements PoolArenaMetric {
...
private final PoolChunkList<T> q050;
private final PoolChunkList<T> q025;
private final PoolChunkList<T> q000;
private final PoolChunkList<T> qInit;
private final PoolChunkList<T> q075;
private final PoolChunkList<T> q100;
...
protected PoolArena(PooledByteBufAllocator parent, int pageSize, int maxOrder, int pageShifts, int chunkSize) {
...
q100 = new PoolChunkList<T>(null, 100, Integer.MAX_VALUE, chunkSize);
q075 = new PoolChunkList<T>(q100, 75, 100, chunkSize);
q050 = new PoolChunkList<T>(q075, 50, 100, chunkSize);
q025 = new PoolChunkList<T>(q050, 25, 75, chunkSize);
q000 = new PoolChunkList<T>(q025, 1, 50, chunkSize);
qInit = new PoolChunkList<T>(q000, Integer.MIN_VALUE, 25, chunkSize);
q100.prevList(q075);
q075.prevList(q050);
q050.prevList(q025);
q025.prevList(q000);
q000.prevList(null);
qInit.prevList(qInit);
...
}
}
通过以下代码我们可以知道,一个Arena里面有6个ChunkList:
private final PoolChunkList<T> q050;
private final PoolChunkList<T> q025;
private final PoolChunkList<T> q000;
private final PoolChunkList<T> qInit;
private final PoolChunkList<T> q075;
private final PoolChunkList<T> q100;
通过一下代码我么可以知道,q100里面的Chunk的内存已经使用100%以上,q075里面的Chunk的内存已经使用75%到100%之间,q050代表它里面的Chunk的内存已经使用50%到100%之间,以此类推。所以netty就可以通过这种方式找到需要的特定的内存,提高效率。
q100 = new PoolChunkList<T>(null, 100, Integer.MAX_VALUE, chunkSize);
q075 = new PoolChunkList<T>(q100, 75, 100, chunkSize);
q050 = new PoolChunkList<T>(q075, 50, 100, chunkSize);
q025 = new PoolChunkList<T>(q050, 25, 75, chunkSize);
q000 = new PoolChunkList<T>(q025, 1, 50, chunkSize);
qInit = new PoolChunkList<T>(q000, Integer.MIN_VALUE, 25, chunkSize);
看PoolChunkList的构造函数:
PoolChunkList(PoolChunkList<T> nextList, int minUsage, int maxUsage, int chunkSize) {
assert minUsage <= maxUsage;
this.nextList = nextList;
this.minUsage = minUsage;
this.maxUsage = maxUsage;
maxCapacity = calculateMaxCapacity(minUsage, chunkSize);
}
this.nextList=nextList,结合上一段的代码,我们可以知道chunlist之间是这么连接的:
qInit->q00->q025->q050->q075->q100
然后通过下面的代码:
q100.prevList(q075);
q075.prevList(q050);
q050.prevList(q025);
q025.prevList(q000);
q000.prevList(null);
qInit.prevList(qInit);
我们知道最终这些Chunlist之间是这样连接的双向链表:
qInit<=>q00<=>q025<=>q050<=>q075<=>q100
如果我们要分配的内存小于Chunk也就是16M,我们用chunk来分配不划算,浪费空间,所以就有了page。一个page的大小是8k,如果我们要分配内存是10k,那么我们用chunk里面的两个page来分配就很划算了。但是如果只要2k呢,一个page也显得不划算,所有又有了subpage。subpage的大小介于0~8k之间。我们看到PoolArena里面有个:
private final PoolSubpage<T>[] tinySubpagePools;
private final PoolSubpage<T>[] smallSubpagePools;
这就是subpage,点击去:
final class PoolSubpage<T> implements PoolSubpageMetric {
final PoolChunk<T> chunk;
private final int memoryMapIdx;
private final int runOffset;
private final int pageSize;
private final long[] bitmap;
PoolSubpage<T> prev;
PoolSubpage<T> next;
boolean doNotDestroy;
int elemSize;
private int maxNumElems;
private int bitmapLength;
private int nextAvail;
private int numAvail;
}
留意到有个elemSize,这个就是subpage的大小。可以为0~8k之间的值(当然要小于8k)
然后又个bitmap,这里先提前透露,bitmap里面如果值为1,就说明被分配了,如果为0就未分配,后面还会细讲。
然后又prev和next指针,说明subpage之间也就通过双向链表链接的。
然后第一个成员变量final PoolChunk<T> chunk,说明了这个subpage属于哪个chunk。