Netty之内存管理
目录
Netty内存规格介绍
内存区间划分
内存分配单位
内存分配
命中缓存分配
申请新内存分配
ByteBuf的释放与回收
Netty内存规格介绍
内存区间划分
- tiny:0~512B
- small:512B~8K
- normal:8K~16M
- huge:16M以上
内存分配单位
- Chunk:Netty中所有内存都是以Chunk为单位分配的,一个Chunk有16M,例如当前需要1M内存,那么就需要向系统申请一个Chunk单位的内存,然后再从这个Chunk中进一步划分。
- Page:Chunk的划分单位为Page,一个Page有8K,那么一个Chunk就可以划分出2048个Page。
- SubPage:有时候我们需要的内存远达不到一个Page的大小,那么Netty根据实际需要对Page进一步划分成SubPage。
内存分配
内存分配的逻辑在PoolArena的allocate方法中。
private void allocate(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity) {
final int normCapacity = normalizeCapacity(reqCapacity);
// < 8K
if (isTinyOrSmall(normCapacity)) { // capacity < pageSize
int tableIdx;
PoolSubpage<T>[] table;
boolean tiny = isTiny(normCapacity);
if (tiny) { // < 512
if (cache.allocateTiny(this, buf, reqCapacity, normCapacity)) {
// was able to allocate out of the cache so move on
return;
}
tableIdx = tinyIdx(normCapacity);
table = tinySubpagePools;
} else {
if (cache.allocateSmall(this, buf, reqCapacity, normCapacity)) {
// was able to allocate out of the cache so move on
return;
}
tableIdx = smallIdx(normCapacity);
table = smallSubpagePools;
}
final PoolSubpage<T> head = table[tableIdx];
/**
* Synchronize on the head. This is needed as {@link PoolChunk#allocateSubpage(int)} and
* {@link PoolChunk#free(long)} may modify the doubly linked list as well.
*/
synchronized (head) {
final PoolSubpage<T> s = head.next;
if (s != head) {
assert s.doNotDestroy && s.elemSize == normCapacity;
long handle = s.allocate();
assert handle >= 0;
s.chunk.initBufWithSubpage(buf, handle, reqCapacity);
if (tiny) {
allocationsTiny.increment();
} else {
allocationsSmall.increment();
}
return;
}
}
allocateNormal(buf, reqCapacity, normCapacity);
return;
}
// <= 16M
if (normCapacity <= chunkSize) {
if (cache.allocateNormal(this, buf, reqCapacity, normCapacity)) {
// was able to allocate out of the cache so move on
return;
}
allocateNormal(buf, reqCapacity, normCapacity);
} else {
// Huge allocations are never served via the cache so just call allocateHuge
allocateHuge(buf, reqCapacity);
}
}
代码中的逻辑很清晰,根据传入的reqCapacity大小选择对应的内存规格。
tiny or small:< 8KB
tiny:< 512B
small:512B ~ 8KB
normal:<= 16MB
huge:> 16MB
命中缓存分配
通过上面的代码可以发现,PoolArena在分配过程中会先从缓存中寻找,看是否有可用的ByteBuf,如果没有再从新申请。这个缓存的数据结构就是MemoryRegionCache。
MemoryRegionCache数据结构
private abstract static class MemoryRegionCache<T> {
private final int size;
private final Queue<Entry<T>> queue;
private final SizeClass sizeClass;
private int allocations;
MemoryRegionCache(int size, SizeClass sizeClass) {
this.size = MathUtil.safeFindNextPositivePowerOfTwo(size);
queue = PlatformDependent.newFixedMpscQueue(this.size);
this.sizeClass = sizeClass;
}
......
static final class Entry<T> {
final Handle<Entry<?>> recyclerHandle;
PoolChunk<T> chunk;
long handle = -1;
Entry(Handle<Entry<?>> recyclerHandle) {
this.recyclerHandle = recyclerHandle;
}
void recycle() {
chunk = null;
handle = -1;
recyclerHandle.recycle(this);
}
}
......
}
queue:由Entry组成的队列,Entry又是于handle和chunk组成,Netty中所有内存都是以chunk为单位进行分配的,handle指向了一段连续的内存,通过chunk与handle就可以确定一段内存的大小和位置。
sizeClass:指的是按大小划分的三种内存规格。tiny(0~512B)、small(512B~8K)、normal(8K~16M)。huge不会被缓存,所以不在这里。
size:在同一个MemoryRegionCache的queue中,所有元素的大小都是一致的。这个size指定的就是这个统一的大小,也可以理解为是在sizeClass基础上进一步划分的结果。
规格 | queue数量 | 元素大小分类 |
tiny | 32 | 16B、32B、48B……480B、496B(N*16B) |
small | 4 | 512K、1K、2K、4K |
normal | 3 | 8K、16、32K |
可以看到Netty对内存分配的划分是非常细致的,这样才能尽可能的保证内存的连续性,从而提升效率。
MemoryRegionCache的分类与创建
我们知道每一个线程都会维护一个PoolThreadCache对象,那么对应的MemoryRegionCache也维护在这里。
final class PoolThreadCache {
......
// 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;
......
}
这里heap与direct都分别有tiny、small、normal三种类型的cache数组。
数组初始化的逻辑在PoolThreadCache的构造函数中:
PoolThreadCache(PoolArena<byte[]> heapArena, PoolArena<ByteBuffer> directArena,
int tinyCacheSize, int smallCacheSize, int normalCacheSize,
int maxCachedBufferCapacity, int freeSweepAllocationThreshold) {
......
if (directArena != null) {
tinySubPageDirectCaches = createSubPageCaches(
tinyCacheSize, PoolArena.numTinySubpagePools, SizeClass.Tiny);
smallSubPageDirectCaches = createSubPageCaches(
smallCacheSize, directArena.numSmallSubpagePools, SizeClass.Small);
numShiftsNormalDirect = log2(directArena.pageSize);
normalDirectCaches = createNormalCaches(
normalCacheSize, maxCachedBufferCapacity, directArena);
directArena.numThreadCaches.getAndIncrement();
} else {
// No directArea is configured so just null out all caches
tinySubPageDirectCaches = null;
smallSubPageDirectCaches = null;
normalDirectCaches = null;
numShiftsNormalDirect = -1;
}
......
}
命中缓存分配总结:
- 找到对应size的MemoryRegionCache。
- 从queue中弹出一个entry给ByteBuf初始化。
- 将entry放回Recycler对象池进行复用。
申请新内存分配
当无法命中缓存时,就需要去系统申请新的内存,PoolArena会按照不同内存规格范围,根据chunk、page、subpage几个级别按照一定算法去分配。
ByteBuf的释放与回收
- 将连续的内存区段加入到缓存。这里连续的内存可理解为上面提到的entry对象,由它的handle与chunk属性确定位置与大小,结合所属的内存规格加入到对应的缓存queue中。
- 如果缓存队列已经满了,则将这块内存标记为未使用。
- 将使用过的ByteBuf对象加入到Recycler对象池。
还没有评论,来说两句吧...