?
Netty源碼分析第五章: ByteBuf
?
第五節:?directArena分配緩沖區概述
?
上一小節簡單分析了PooledByteBufAllocator中, 線程局部緩存和arean的相關邏輯, 這一小節簡單分析下directArena分配緩沖區的相關過程
回到newDirectBuffer中:
protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) {PoolThreadCache cache = threadCache.get();PoolArena<ByteBuffer> directArena = cache.directArena;ByteBuf buf;if (directArena != null) { buf = directArena.allocate(cache, initialCapacity, maxCapacity);} else {if (PlatformDependent.hasUnsafe()) {buf = UnsafeByteBufUtil.newUnsafeDirectByteBuf(this, initialCapacity, maxCapacity);} else {buf = new UnpooledDirectByteBuf(this, initialCapacity, maxCapacity);}}return toLeakAwareBuffer(buf);
}
獲取了directArena對象之后, 通過allocate方法分配一個ByteBuf, 這里allocate方法是PoolArena類中的方法
跟到allocate方法中:
PooledByteBuf<T> allocate(PoolThreadCache cache, int reqCapacity, int maxCapacity) { PooledByteBuf<T> buf = newByteBuf(maxCapacity); allocate(cache, buf, reqCapacity);return buf;
}
首先通過newByteBuf獲得一個ByteBuf對象
再通過allocate方法進行分配, 這里要注意, 這里進行分配的時候是線程私有的directArena進行分配
我們跟到newByteBuf方法中
因為是directArena調用的newByteBuf, 所以這里會進入DirectArena類的newByteBuf中:
protected PooledByteBuf<ByteBuffer> newByteBuf(int maxCapacity) { if (HAS_UNSAFE) { return PooledUnsafeDirectByteBuf.newInstance(maxCapacity);} else {return PooledDirectByteBuf.newInstance(maxCapacity);}
}
因為默認通常是有unsafe對象的, 所以這里會走到這一步中PooledUnsafeDirectByteBuf.newInstance(maxCapacity)
通過靜態方法newInstance創建一個PooledUnsafeDirectByteBuf對象
跟到newInstance方法中:
static PooledUnsafeDirectByteBuf newInstance(int maxCapacity) {PooledUnsafeDirectByteBuf buf = RECYCLER.get();buf.reuse(maxCapacity);return buf;
}
這里通過RECYCLER.get()這種方式拿到一個ByteBuf對象, RECYCLER其實是一個對象回收站, 這部分內容會在后面的內容中詳細剖析, 這里我們只需要知道, 這種方式能從回收站中拿到一個對象, 如果回收站里沒有相關對象, 則創建一個新
因為這里有可能是從回收站中拿出的一個對象, 所以通過reuse進行復用
跟到reuse方法中:
final void reuse(int maxCapacity) {maxCapacity(maxCapacity);setRefCnt(1);setIndex0(0, 0);discardMarks();
}
這里設置了的最大可擴容內存, 對象的引用數量, 讀寫指針位置都重置為0, 以及讀寫指針的位置標記也都重置為0
我們回到PoolArena的allocate方法中:
PooledByteBuf<T> allocate(PoolThreadCache cache, int reqCapacity, int maxCapacity) { PooledByteBuf<T> buf = newByteBuf(maxCapacity); allocate(cache, buf, reqCapacity);return buf;
}
拿到了ByteBuf對象, 就可以通過allocate(cache, buf, reqCapacity)方法進行內存分配了
跟到allocate方法中:
private void allocate(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity) {//規格化final int normCapacity = normalizeCapacity(reqCapacity);if (isTinyOrSmall(normCapacity)) { int tableIdx;PoolSubpage<T>[] table;//判斷是不是tintyboolean tiny = isTiny(normCapacity);if (tiny) { // < 512//緩存分配if (cache.allocateTiny(this, buf, reqCapacity, normCapacity)) {return;}//通過tinyIdx拿到tableIdxtableIdx = tinyIdx(normCapacity);//subpage的數組table = tinySubpagePools;} else {if (cache.allocateSmall(this, buf, reqCapacity, normCapacity)) {return;}tableIdx = smallIdx(normCapacity);table = smallSubpagePools;}//拿到對應的節點final PoolSubpage<T> head = table[tableIdx];synchronized (head) {final PoolSubpage<T> s = head.next;//默認情況下, 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;}if (normCapacity <= chunkSize) {//首先在緩存上進行內存分配if (cache.allocateNormal(this, buf, reqCapacity, normCapacity)) {//分配成功, 返回return;}//分配不成功, 做實際的內存分配
allocateNormal(buf, reqCapacity, normCapacity);} else {//大于這個值, 就不在緩存上分配
allocateHuge(buf, reqCapacity);}
}
這里看起來邏輯比較長, 其實主要步驟分為兩步
1.首先在緩存上進行分配, 對應步驟是:
? cache.allocateTiny(this, buf, reqCapacity, normCapacity)
? cache.allocateSmall(this, buf, reqCapacity, normCapacity)
? cache.allocateNormal(this, buf, reqCapacity, normCapacity)
2.如果在緩存上分配不成功, 則實際分配一塊內存, 對應步驟是
? allocateNormal(buf, reqCapacity, normCapacity)
在這里對幾種類型的內存進行介紹:
之前的小節我們介紹過, 緩沖區內存類型分為tiny, small, 和normal, 其實還有種不常見的類型叫做huge, 那么這幾種類型的內存有什么區別呢, 實際上這幾種類型是按照緩沖區初始化空間的范圍進行區分的, 具體區分如下:
tiny類型對應的緩沖區范圍為0-512B
small類型對應的緩沖區范圍為512B-8K
normal類型對應的緩沖區范圍為8K-16MB
huge類型對應緩沖區范圍為大于16MB
簡單介紹下有關范圍的含義:
16MB對應一個chunk, netty是以chunk為單位向操作系統申請內存的
8k對應一個page, page是將chunk切分后的結果, 一個chunk對應2048個page
8k以下對應一個subpage, subpage是page的切分, 一個page可以切分多個subpage, 具體切分幾個需要根據subpage的大小而定, 比如只要分配1k的緩沖區, 則會將page切分成8個subpage
以上就是directArena內存分配的大概流程和相關概念
?
上一節: PooledByteBufAllocator簡述
下一節: 命中緩存的分配
?