Netty源码之命中缓存的逻辑分配
由于Netty在分配内存的时候首先会尝试从缓存中分配内存,所以这节我们就来看一下,它是怎么从缓存中进行分配的。
这个数据结构是Netty中和内存缓存有关的数据结构,它由三部分组成:queue、sizeClass、size。queue中的每一个元素都是一个entity,每个entity中都有一个chunk和一个handler,handler用来执行一个唯一连续的内存。sizeClass代表着内存规格。size就代表着缓存数组的大小。
这个就是内存缓存的架构,存储在当前线程的PooledThreadCache中,从这张图上我们就可以清楚的看到,上面的每个节点都对应着一个queue。tiny一共有32个数组元素,每个数组元素存储的内存间隔是16B,small规格的缓存数组一共4个,内存的间隔上一个的2倍。normal规格的只缓存了三中类型的内存大小,也是以2的倍数递增。
假设我们要分配一个14B的内存,首先将这个14B进行规范化变成16B,然后定位到他是tiny规格的,之后就会在tiny对应的缓存数据结构中找到16B对应的queue,从里面拿出一个entity分配给buf。
我们回到allocate这段代码里面,从源码中来感受一下:
cache.allocateTiny(this, buf, reqCapacity, normCapacity)
这行代码是对tiny进行缓存分配,其他内存规格的内存分配逻辑都差不多,在这我们就以tiny为例子:
boolean allocateTiny(PoolArena<?> area, PooledByteBuf<?> buf, int reqCapacity, int normCapacity) {
return allocate(cacheForTiny(area, normCapacity), buf, reqCapacity);
}
进入这个方法后有继续调用了allocate方法,只不过,再传的参数里面对area和normaCapacity进行了处理:
private MemoryRegionCache<?> cacheForTiny(PoolArena<?> area, int normCapacity) {
int idx = PoolArena.tinyIdx(normCapacity);
if (area.isDirect()) {
return cache(tinySubPageDirectCaches, idx);
}
return cache(tinySubPageHeapCaches, idx);
}
原来这个方法就是先定位到当前规格化的内存大小在tiny的那个数组下标上,找到之后根据申请的是直接内存还是堆内存从各自的缓存数据结构中查找,继续跟进去看一下:
private static <T> MemoryRegionCache<T> cache(MemoryRegionCache<T>[] cache, int idx) {
if (cache == null || idx > cache.length - 1) {
return null;
}
return cache[idx];
}
直接返回对应缓存数组中的内存段,无论是否为空。
private boolean allocate(MemoryRegionCache<?> cache, PooledByteBuf buf, int reqCapacity) {
//如果我们拿到的缓存是null,那么缓存分配失败,那么就直接返回false
if (cache == null) {
// no cache found so just return false here
return false;
}
//到这里,就说明缓存中有对应的内存,那么就进行分配
boolean allocated = cache.allocate(buf, reqCapacity);
if (++ allocations >= freeSweepAllocationThreshold) {
allocations = 0;
trim();
}
return allocated;
}
在正式分配之前,首先对我们获取的缓存进行了验证,然后才开始正式分配:
public final boolean allocate(PooledByteBuf<T> buf, int reqCapacity) {
//从queue中弹出一个实体
Entry<T> entry = queue.poll();
//对实体进行验证
if (entry == null) {
return false;
}
//开始分配内存
initBuf(entry.chunk, entry.handle, buf, reqCapacity);
//将这个实体放到对象池中进行利用
entry.recycle();
++ allocations;
return true;
}
在这个实体中保存着当前内存段处于哪个chunk中,还保存着要给handle,它记录着当前内存段的具体位置。之后通过这个实体对buf进行初始化:
protected void initBuf(
PoolChunk<T> chunk, long handle, PooledByteBuf<T> buf, int reqCapacity) {
chunk.initBufWithSubpage(buf, handle, reqCapacity);
}
最后是调用了chunk里面的方法进行初始化:
private void initBufWithSubpage(PooledByteBuf<T> buf, long handle, int bitmapIdx, int reqCapacity) {
assert bitmapIdx != 0;
//获得当前分配的page是第几个
int memoryMapIdx = memoryMapIdx(handle);
//计算得到这是第几个subpage
PoolSubpage<T> subpage = subpages[subpageIdx(memoryMapIdx)];
assert subpage.doNotDestroy;
assert reqCapacity <= subpage.elemSize;
//进行初始化
buf.init(
this, handle,
runOffset(memoryMapIdx) + (bitmapIdx & 0x3FFFFFFF) * subpage.elemSize, reqCapacity, subpage.elemSize,
arena.parent.threadCache());
}
这个方法根据分配的page的id找到subpages数组中对应的subpage,然后利用subpage进行初始化:
void init(PoolChunk<T> chunk, long handle, int offset, int length, int maxLength, PoolThreadCache cache) {
assert handle >= 0;
assert chunk != null;
this.chunk = chunk;
this.handle = handle;
memory = chunk.memory;
this.offset = offset;
this.length = length;
this.maxLength = maxLength;
tmpNioBuf = null;
this.cache = cache;
}
直接把缓存内存的信息赋值给buf。
这就是通过缓存来进行分配,下一节中我们将看一下,在缓存中拿不到之后从内存池中申请的方式。