目錄
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對象池。