PoolArena
?是 Netty 內存池化機制的核心組件之一,它負責管理一整塊或多塊內存(PoolChunk
),并將這些內存分配給應用程序。每個?PoolArena
?實例都與一個特定的線程相關聯(通過?PoolThreadCache
),或者在禁用線程緩存時被多個線程共享。Netty 會創建多個?PoolArena
?來減少多線程環境下的鎖競爭。
PoolArena
?是一個抽象類,它有兩個具體的子類:
HeapArena
: 用于分配堆內存 (byte[]
)。DirectArena
: 用于分配直接內存 (ByteBuffer
)。
主要成員變量和職責
讓我們看一下?PoolArena
?類中的一些關鍵字段:
// ...
abstract class PoolArena<T> implements PoolArenaMetric {// ...enum SizeClass {Small,Normal}final PooledByteBufAllocator parent; // 指向創建此 Arena 的 PooledByteBufAllocatorfinal PoolSubpage<T>[] smallSubpagePools; // 用于管理 Small 類型內存分配的 PoolSubpage 池數組// PoolChunkList 用于根據 PoolChunk 的使用率將其組織起來// qInit: 0-25% 使用率 (最初創建的 Chunk)// q000: < 50% 使用率// q025: 25-75% 使用率// q050: 50-100% 使用率// q075: 75-100% 使用率// q100: 100% 使用率 (已滿,但仍可分配 Subpage)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;private final List<PoolChunkListMetric> chunkListMetrics; // 用于收集 PoolChunkList 的度量信息// 各種分配和釋放的計數器private long allocationsNormal; // Normal 類型分配次數private final LongAdder allocationsSmall = new LongAdder(); // Small 類型分配次數 (線程安全)private final LongAdder allocationsHuge = new LongAdder(); // Huge 類型分配次數 (線程安全)private final LongAdder activeBytesHuge = new LongAdder(); // Huge 類型活躍字節數 (線程安全)private long deallocationsSmall; // Small 類型釋放次數private long deallocationsNormal; // Normal 類型釋放次數private long pooledChunkAllocations; // 池化 Chunk 的分配次數private long pooledChunkDeallocations; // 池化 Chunk 的釋放次數private final LongAdder deallocationsHuge = new LongAdder(); // Huge 類型釋放次數 (線程安全)// 使用此 Arena 的線程緩存數量final AtomicInteger numThreadCaches = new AtomicInteger();private final ReentrantLock lock = new ReentrantLock(); // 用于保護 Arena 內部狀態的鎖final SizeClasses sizeClass; // 描述了 Arena 的大小規格配置 (pageSize, chunkSize 等)// ...
}
SizeClass
: 枚舉類型,表示內存分配的類型,分為?Small
?(小于等于?pageSize / 2
,通常從?PoolSubpage
?分配) 和?Normal
?(大于?pageSize / 2
?但小于?chunkSize
,直接從?PoolChunk
?分配)。parent
: 指向?PooledByteBufAllocator
,這是內存分配器的頂層入口。smallSubpagePools
: 這是一個?PoolSubpage
?數組,數組的每個元素是一個雙向鏈表的頭節點。相同大小的?Small
?類型的?PoolSubpage
?會被鏈接到同一個鏈表上,便于快速查找和分配。qInit
,?q000
,?q025
,?q050
,?q075
,?q100
: 這些是?PoolChunkList
?對象,它們形成了一個雙向鏈表結構。PoolArena
?根據?PoolChunk
?的內存使用率(usage()
)將其組織在不同的?PoolChunkList
?中。例如,q050
?存儲使用率在 50% 到 100% 之間的?PoolChunk
。這種組織方式有助于在分配內存時,優先從使用率較高的?PoolChunk
?中分配,以期盡快填滿并釋放空閑的?PoolChunk
,從而減少內存碎片。- Metrics Counters: 大量的計數器用于追蹤不同類型(Small, Normal, Huge)的分配和釋放次數,以及活躍的字節數和 Chunk 數量。這些信息對于監控內存池的性能和狀態非常有用。
LongAdder
?用于在高并發場景下提供比?AtomicLong
?更好的性能。 numThreadCaches
: 記錄了當前有多少個?PoolThreadCache
?正在使用這個?PoolArena
。lock
: 一個可重入鎖,用于在修改?PoolArena
?的共享數據結構(如?PoolChunkList
)時進行同步,防止并發沖突。sizeClass
: (實際上是?this.sizeClass
,來自構造函數參數?SizeClasses sizeClass
) 這是一個?SizeClasses
?對象,它封裝了關于內存規格的配置信息,如?pageSize
(頁大小)、pageShifts
、chunkSize
(塊大小)等,并提供了一些計算方法,如根據請求大小計算規格索引 (size2SizeIdx
)。
構造函數
PoolArena.java
// ...protected PoolArena(PooledByteBufAllocator parent, SizeClasses sizeClass) {assert null != sizeClass;this.parent = parent;this.sizeClass = sizeClass;smallSubpagePools = newSubpagePoolArray(sizeClass.nSubpages);for (int i = 0; i < smallSubpagePools.length; i ++) {smallSubpagePools[i] = newSubpagePoolHead(i);}q100 = new PoolChunkList<T>(this, null, 100, Integer.MAX_VALUE, sizeClass.chunkSize);q075 = new PoolChunkList<T>(this, q100, 75, 100, sizeClass.chunkSize);q050 = new PoolChunkList<T>(this, q100, 50, 100, sizeClass.chunkSize); // 注意這里 nextList 是 q100q025 = new PoolChunkList<T>(this, q050, 25, 75, sizeClass.chunkSize);q000 = new PoolChunkList<T>(this, q025, 1, 50, sizeClass.chunkSize);qInit = new PoolChunkList<T>(this, q000, Integer.MIN_VALUE, 25, sizeClass.chunkSize);q100.prevList(q075);q075.prevList(q050);q050.prevList(q025);q025.prevList(q000);q000.prevList(null); // q000 的前一個 List 是 null,表示它是鏈表的頭部(在查找時)qInit.prevList(qInit); // qInit 的 prevList 指向自身,它是一個特殊的 ListList<PoolChunkListMetric> metrics = new ArrayList<PoolChunkListMetric>(6);metrics.add(qInit);metrics.add(q000);metrics.add(q025);metrics.add(q050);metrics.add(q075);metrics.add(q100);chunkListMetrics = Collections.unmodifiableList(metrics);}private PoolSubpage<T> newSubpagePoolHead(int index) {PoolSubpage<T> head = new PoolSubpage<T>(index);head.prev = head;head.next = head;return head;}@SuppressWarnings("unchecked")private PoolSubpage<T>[] newSubpagePoolArray(int size) {return new PoolSubpage[size];}
// ...
構造函數主要做了以下幾件事:
- 初始化?
parent
?和?sizeClass
。 - 初始化?
smallSubpagePools
?數組,其中每個元素都是一個?PoolSubpage
?鏈表的頭節點。newSubpagePoolHead
?創建一個空的雙向循環鏈表。 - 初始化?
qInit
?到?q100
?這些?PoolChunkList
。注意它們的?minUsage
?和?maxUsage
?參數,以及它們之間的?nextList
?和?prevList
?關系,形成了一個查找鏈。qInit
: 用于存放新創建的?PoolChunk
,使用率范圍是?Integer.MIN_VALUE
?到?25%
。q000
: 使用率?1%
?到?50%
。q025
: 使用率?25%
?到?75%
。q050
: 使用率?50%
?到?100%
。q075
: 使用率?75%
?到?100%
。q100
: 使用率?100%
?到?Integer.MAX_VALUE
?(實際上是100%)。 這些?PoolChunkList
?通過?prevList
?和?nextList
?鏈接起來,方便在分配和釋放時根據?PoolChunk
?的使用率變化將其移動到合適的?PoolChunkList
?中。
內存分配 (allocate
)
內存分配是?PoolArena
?的核心功能。
PoolArena.java
// ...PooledByteBuf<T> allocate(PoolThreadCache cache, int reqCapacity, int maxCapacity) {PooledByteBuf<T> buf = newByteBuf(maxCapacity); // 創建一個 PooledByteBuf 對象 (具體類型由子類決定)allocate(cache, buf, reqCapacity); // 調用內部的分配方法return buf;}private void allocate(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity) {final int sizeIdx = sizeClass.size2SizeIdx(reqCapacity); // 根據請求容量計算規格索引if (sizeIdx <= sizeClass.smallMaxSizeIdx) { // Small 類型分配tcacheAllocateSmall(cache, buf, reqCapacity, sizeIdx);} else if (sizeIdx < sizeClass.nSizes) { // Normal 類型分配tcacheAllocateNormal(cache, buf, reqCapacity, sizeIdx);} else { // Huge 類型分配 (大于 chunkSize)int normCapacity = sizeClass.directMemoryCacheAlignment > 0? sizeClass.normalizeSize(reqCapacity) : reqCapacity;// Huge allocations are never served via the cache so just call allocateHugeallocateHuge(buf, normCapacity);}}
// ...
allocate(PoolThreadCache cache, int reqCapacity, int maxCapacity)
: 這是外部調用的入口。它首先通過?newByteBuf(maxCapacity)
?創建一個?PooledByteBuf
?實例(具體是?PooledHeapByteBuf
?還是?PooledDirectByteBuf
?等由子類實現),然后調用內部的?allocate
?方法來實際分配內存。allocate(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity)
:- 首先,根據請求容量?
reqCapacity
?計算出對應的?sizeIdx
?(size index)。 - Small Allocation: 如果?
sizeIdx
?小于等于?smallMaxSizeIdx
?(通常是?pageSize / 2
?對應的索引),則認為是小內存分配,調用?tcacheAllocateSmall
。 - Normal Allocation: 如果?
sizeIdx
?大于?smallMaxSizeIdx
?但小于?nSizes
?(總規格數,對應?chunkSize
?的索引),則認為是普通內存分配,調用?tcacheAllocateNormal
。 - Huge Allocation: 如果?
sizeIdx
?超出了?nSizes
,表示請求的內存大于?chunkSize
,則認為是大內存分配,調用?allocateHuge
。大內存分配不會使用線程緩存,并且會創建一個獨立的、非池化的?PoolChunk
。
- 首先,根據請求容量?
tcacheAllocateSmall
?(Small 類型分配)
PoolArena.java
// ...private void tcacheAllocateSmall(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity,final int sizeIdx) {if (cache.allocateSmall(this, buf, reqCapacity, sizeIdx)) { // 嘗試從線程緩存分配// was able to allocate out of the cache so move onreturn;}/** 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.*/final PoolSubpage<T> head = smallSubpagePools[sizeIdx]; // 獲取對應 sizeIdx 的 Subpage 鏈表頭final boolean needsNormalAllocation;head.lock(); // 對 Subpage 鏈表頭加鎖try {final PoolSubpage<T> s = head.next;needsNormalAllocation = s == head; // 如果鏈表為空,則需要進行 Normal Allocation 來創建新的 Subpageif (!needsNormalAllocation) {assert s.doNotDestroy && s.elemSize == sizeClass.sizeIdx2size(sizeIdx) : "doNotDestroy=" +s.doNotDestroy + ", elemSize=" + s.elemSize + ", sizeIdx=" + sizeIdx;long handle = s.allocate(); // 從 Subpage 中分配一個元素assert handle >= 0;s.chunk.initBufWithSubpage(buf, null, handle, reqCapacity, cache); // 初始化 ByteBuf}} finally {head.unlock();}if (needsNormalAllocation) { // 如果沒有可用的 Subpagelock(); // 獲取 Arena 的全局鎖try {allocateNormal(buf, reqCapacity, sizeIdx, cache); // 進行 Normal Allocation (可能會創建新的 Chunk 和 Subpage)} finally {unlock();}}incSmallAllocation(); // 增加 Small 分配計數}
// ...
- 首先嘗試從?
PoolThreadCache
?中分配。如果成功,則直接返回。 - 如果線程緩存分配失敗,則從?
smallSubpagePools
?中查找對應?sizeIdx
?的?PoolSubpage
?鏈表。 - 對該鏈表的頭節點?
head
?加鎖(head.lock()
),這是為了保護?PoolSubpage
?鏈表的并發修改。 - 如果鏈表中有可用的?
PoolSubpage
?(s != head
),則從該?PoolSubpage
?(s
) 中調用?s.allocate()
?分配一個元素(得到一個?handle
),然后用這個?handle
?初始化?PooledByteBuf
。 - 如果鏈表為空 (
s == head
),說明當前沒有合適的?PoolSubpage
?可供分配。此時,needsNormalAllocation
?為?true
。 - 釋放?
head
?的鎖。 - 如果?
needsNormalAllocation
?為?true
,則需要進行一次“普通分配”(allocateNormal
)。這通常意味著需要從某個?PoolChunk
?中分配一個新的?PoolSubpage
。這個過程需要獲取?PoolArena
?的全局鎖 (lock()
)。 - 最后,增加?
allocationsSmall
?計數。
tcacheAllocateNormal
?(Normal 類型分配)
PoolArena.java
// ...private void tcacheAllocateNormal(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity,final int sizeIdx) {if (cache.allocateNormal(this, buf, reqCapacity, sizeIdx)) { // 嘗試從線程緩存分配// was able to allocate out of the cache so move onreturn;}lock(); // 獲取 Arena 的全局鎖try {allocateNormal(buf, reqCapacity, sizeIdx, cache); // 進行 Normal Allocation++allocationsNormal; // 增加 Normal 分配計數} finally {unlock();}}
// ...
- 首先嘗試從?
PoolThreadCache
?中分配。如果成功,則直接返回。 - 如果線程緩存分配失敗,則獲取?
PoolArena
?的全局鎖 (lock()
)。 - 調用?
allocateNormal
?方法進行實際的分配。 - 增加?
allocationsNormal
?計數。 - 釋放鎖。
allocateNormal
?(核心普通分配邏輯)
// ...private void allocateNormal(PooledByteBuf<T> buf, int reqCapacity, int sizeIdx, PoolThreadCache threadCache) {assert lock.isHeldByCurrentThread(); // 確認當前線程已持有 Arena 鎖if (q050.allocate(buf, reqCapacity, sizeIdx, threadCache) || // 嘗試從 q050 分配q025.allocate(buf, reqCapacity, sizeIdx, threadCache) || // 嘗試從 q025 分配q000.allocate(buf, reqCapacity, sizeIdx, threadCache) || // 嘗試從 q000 分配qInit.allocate(buf, reqCapacity, sizeIdx, threadCache) || // 嘗試從 qInit 分配q075.allocate(buf, reqCapacity, sizeIdx, threadCache)) { // 最后嘗試從 q075 分配 (q100 通常是滿的)return;}// Add a new chunk.// 如果所有 PoolChunkList 都分配失敗,則創建一個新的 PoolChunkPoolChunk<T> c = newChunk(sizeClass.pageSize, sizeClass.nPSizes, sizeClass.pageShifts, sizeClass.chunkSize);boolean success = c.allocate(buf, reqCapacity, sizeIdx, threadCache); // 從新 Chunk 中分配assert success; // 新創建的 Chunk 必然能分配成功qInit.add(c); // 將新 Chunk 加入到 qInit 列表++pooledChunkAllocations; // 增加 Chunk 分配計數}
// ...
此方法在持有?PoolArena
?全局鎖的情況下執行:
- 按順序嘗試從?
PoolChunkList
?分配:q050
?(50-100% usage)q025
?(25-75% usage)q000
?(1-50% usage)qInit
?(newly created chunks, <25% usage)q075
?(75-100% usage)- (
q100
?列表中的?PoolChunk
?通常是滿的,但仍可能用于分配?PoolSubpage
,這個邏輯在?PoolChunkList.allocate
?->?PoolChunk.allocate
?中處理)。 這個順序的目的是優先使用那些已經分配了一部分內存的?PoolChunk
,以期更快地填滿它們,從而減少內存碎片。
- 如果上述所有?
PoolChunkList
?都無法成功分配(即它們內部的?PoolChunk
?都沒有足夠的空間或者無法分配出請求大小的內存塊/子頁),則需要創建一個新的?PoolChunk
。 - 創建新?
PoolChunk
: 調用?newChunk(...)
?方法(由子類?HeapArena
?或?DirectArena
?實現)創建一個新的?PoolChunk
。 - 從這個新創建的?
PoolChunk
?中調用?c.allocate(...)
?來分配內存給?buf
。新創建的?PoolChunk
?肯定是空的,所以這次分配一定會成功。 - 將新創建的?
PoolChunk
?添加到?qInit
?列表中。 - 增加?
pooledChunkAllocations
?計數。
為什么對于chunkList 是這樣的分配順序
觀察到的分配順序是 q050 , q025 , q000 , qInit , 最后是 q075 。這個順序并非簡單地按利用率從低到高或從高到低,而是基于一種旨在減少內存碎片的“最佳適應”(Best-Fit)策略的變體,這種策略深受 jemalloc 內存分配器的影響。
首先,我們先明確一下這些 PoolChunkList (也就是 q 系列的鏈表)各自管理的 PoolChunk 的內存使用率范圍。根據 PoolArena.java
中的定義:
-
qInit : 使用率低于 25% 的 Chunk (通常是新創建的)。
-
q000 : 使用率在 1% 到 50% 之間的 Chunk 。
-
q025 : 使用率在 25% 到 75% 之間的 Chunk 。
-
q050 : 使用率在 50% 到 100% 之間的 Chunk 。
-
q075 : 使用率在 75% 到 100% 之間的 Chunk 。
-
q100 : 使用率達到 100% 的 Chunk (已滿)。
現在我們來分析這個分配順序:
-
主要分配策略 ( q050 -> q025 -> q000 -> qInit ) : 這個順序是從使用率較高的 Chunk 列表開始,逐步到使用率較低的列表。這體現了“最佳適應”的思想。 目標是優先填滿那些已經分配了較多內存的 Chunk 。這樣做的好處是:
-
減少內存碎片 :通過集中在少數 Chunk 中進行分配,可以更快地將它們填滿(達到100%使用率),然后將它們移到 q100 鏈表中。一個全滿的 Chunk 不再參與后續的分配,當它內部的所有 ByteBuf 都被釋放后,這個 Chunk 就可以被完全回收,將內存歸還給操作系統。
-
提高效率 :如果總是從利用率最低的 Chunk (如 qInit )開始分配,會導致大量 Chunk 都處于“部分使用”的狀態,使得內存碎片化嚴重,難以分配較大的連續內存塊,并且降低了內存歸還給系統的可能性。
-
-
q075 為什么排在最后 : q075 列表中的 Chunk 使用率已經非常高(75%-100%),意味著它們的剩余空間很小。 allocateNormal 方法用于分配“正常大小”的內存塊(大于 small ,小于 huge ),這種大小的內存在一個幾乎已滿的 Chunk 中找到合適空間的概率較低。
-
性能優化 :將 q075 放在最后檢查是一種性能優化。與其一開始就徒勞地在這些幾乎已滿的 Chunk 中搜索,不如先嘗試其他更有可能成功的 Chunk 列表。只有當其他所有列表都無法滿足分配請求時,才最后嘗試在這些“殘羹剩飯”中尋找機會。這減少了不必要的搜索開銷。
-
總結來說,Netty 的這個分配順序是一個精心設計的權衡:
-
主體順序 ( q050 -> qInit ) 是為了 對抗內存碎片 ,傾向于“物盡其用”,盡快填滿并回收 Chunk 。
-
q075 的特殊位置 是為了 提升分配性能 ,避免在成功率低的 Chunk 上浪費時間。
這種設計使得 PooledByteBufAllocator 在高并發和長時間運行的場景下依然能保持高效和穩定的內存管理。
allocateHuge
?(Huge 類型分配)
內存池的主要優勢在于復用頻繁申請和釋放的小到中等大小的內存塊,以避免頻繁向操作系統申請內存和垃圾回收帶來的開銷。
對于“巨大”(Huge)的內存分配(通常大于 chunkSize ,默認16MB),這種分配本身就不頻繁。如果將這些巨大的內存塊池化,意味著Netty需要長期持有一個或多個非常大的內存塊,即使它們在大部分時間里是空閑的。這會導致嚴重的內存資源浪費,尤其是在高并發環境下,可能會長時間占用大量內存,降低了整體的內存使用效率。
// ...private void allocateHuge(PooledByteBuf<T> buf, int reqCapacity) {PoolChunk<T> chunk = newUnpooledChunk(reqCapacity); // 創建一個非池化的、大小剛好滿足需求的 ChunkactiveBytesHuge.add(chunk.chunkSize()); // 增加 Huge 類型活躍字節數buf.initUnpooled(chunk, reqCapacity); // 用這個非池化 Chunk 初始化 ByteBufallocationsHuge.increment(); // 增加 Huge 分配計數}
// ...
- 調用?
newUnpooledChunk(reqCapacity)
?創建一個非池化的?PoolChunk
。這個?PoolChunk
?的大小就是?reqCapacity
,它不會被放入任何?PoolChunkList
?中,也不會被其他分配請求共享。 - 更新?
activeBytesHuge
?和?allocationsHuge
?計數。 - 調用?
buf.initUnpooled(...)
?來初始化?PooledByteBuf
。
內存釋放 (free
)
// ...void free(PoolChunk<T> chunk, ByteBuffer nioBuffer, long handle, int normCapacity, PoolThreadCache cache) {chunk.decrementPinnedMemory(normCapacity); // 減少 Chunk 的 pinned 內存計數if (chunk.unpooled) { // 如果是 Huge Allocation (非池化 Chunk)int size = chunk.chunkSize();destroyChunk(chunk); // 直接銷毀 ChunkactiveBytesHuge.add(-size); // 更新 Huge 類型活躍字節數deallocationsHuge.increment(); // 更新 Huge 類型釋放計數} else { // 池化 ChunkSizeClass sizeClass = sizeClass(handle); // 判斷是 Small 還是 Normal 釋放if (cache != null && cache.add(this, chunk, nioBuffer, handle, normCapacity, sizeClass)) {// 嘗試將內存在線程緩存中緩存起來// cached so not free it.return;}// 如果線程緩存失敗或沒有緩存,則真正釋放回 ArenafreeChunk(chunk, handle, normCapacity, sizeClass, nioBuffer, false);}}private static SizeClass sizeClass(long handle) {return isSubpage(handle) ? SizeClass.Small : SizeClass.Normal;}void freeChunk(PoolChunk<T> chunk, long handle, int normCapacity, SizeClass sizeClass, ByteBuffer nioBuffer,boolean finalizer) {final boolean destroyChunk;lock(); // 獲取 Arena 全局鎖try {// We only call this if freeChunk is not called because of the PoolThreadCache finalizer as otherwise this// may fail due lazy class-loading in for example tomcat.if (!finalizer) { // 如果不是由 finalizer 觸發的釋放switch (sizeClass) {case Normal:++deallocationsNormal;break;case Small:++deallocationsSmall;break;default:throw new Error();}}// 調用 PoolChunkList 的 free 方法,該方法內部會調用 PoolChunk 的 free// 如果 PoolChunk 完全空閑,則 !chunk.parent.free(...) 返回 true,表示需要銷毀 ChunkdestroyChunk = !chunk.parent.free(chunk, handle, normCapacity, nioBuffer);if (destroyChunk) {// all other destroyChunk calls come from the arena itself being finalized, so don't need to be counted++pooledChunkDeallocations; // 更新 Chunk 釋放計數}} finally {unlock();}if (destroyChunk) {// destroyChunk not need to be called while holding the synchronized lock.destroyChunk(chunk); // 在鎖外銷毀 Chunk}}
// ...
free(...)
:- 首先減少?
chunk
?的?pinnedMemory
?計數。 - 非池化 Chunk (Huge): 如果?
chunk.unpooled
?為?true
,說明是為大內存分配創建的獨立?PoolChunk
。直接調用?destroyChunk(chunk)
?銷毀它,并更新相應的 Huge 類型計數器。 - 池化 Chunk:
- 通過?
isSubpage(handle)
?判斷是?Small
?還是?Normal
?類型的釋放。 - 嘗試將這塊內存添加到?
PoolThreadCache
?中。如果添加成功,則直接返回,內存被緩存了。 - 如果緩存失敗或沒有線程緩存,則調用?
freeChunk(...)
?將內存真正釋放回?PoolArena
。
- 通過?
- 首先減少?
freeChunk(...)
:- 獲取?
PoolArena
?的全局鎖。 - 更新?
deallocationsNormal
?或?deallocationsSmall
?計數(如果不是由?finalizer
?觸發)。 - 調用?
chunk.parent.free(...)
,這里的?chunk.parent
?是指該?PoolChunk
?所在的?PoolChunkList
。PoolChunkList.free(...)
?方法會進一步調用?PoolChunk.free(handle)
?來釋放?PoolChunk
?內部的內存。如果?PoolChunk
?在釋放這塊內存后變為空閑(freeBytes == chunkSize
),PoolChunkList.free(...)
?會將該?PoolChunk
?從鏈表中移除,并返回?false
。因此,destroyChunk
?變量會是?true
。 - 如果?
destroyChunk
?為?true
,增加?pooledChunkDeallocations
?計數。 - 釋放鎖。
- 如果?
destroyChunk
?為?true
,則在鎖外部調用?destroyChunk(chunk)
?來實際銷毀?PoolChunk
(例如,釋放底層的?ByteBuffer
?或?byte[]
)。
- 獲取?
內存重分配 (reallocate
)
reallocate 函數主要在 PooledByteBuf 的容量需要改變,并且無法在當前已分配的內存塊( PoolChunk )內完成調整時被調用。具體來說,調用鏈是這樣的:
-
當調用 ByteBuf.capacity(int newCapacity) 方法來調整一個池化的 ByteBuf (即 PooledByteBuf )的大小時。
-
在
capacity
方法內部,會檢查新的容量 newCapacity 是否可以直接在當前內存區域( maxLength )內容納。 -
如果 newCapacity 超出了當前內存塊能支持的范圍(例如,比當前 length 大,且大于 maxLength ),或者不滿足一些特定的收縮條件,就需要進行真正的內存重分配。此時,它會調用 chunk.arena.reallocate(this, newCapacity) ,也就是我們正在討論的
reallocate
方法。
// ...void reallocate(PooledByteBuf<T> buf, int newCapacity) {assert newCapacity >= 0 && newCapacity <= buf.maxCapacity();final int oldCapacity;final PoolChunk<T> oldChunk;final ByteBuffer oldNioBuffer;final long oldHandle;final T oldMemory;final int oldOffset;final int oldMaxLength;final PoolThreadCache oldCache;// We synchronize on the ByteBuf itself to ensure there is no "concurrent" reallocations for the same buffer.// ...synchronized (buf) { // 對 ByteBuf 對象本身加鎖,防止對同一個 buf 的并發重分配oldCapacity = buf.length;if (oldCapacity == newCapacity) {return;}// 保存舊 buf 的信息oldChunk = buf.chunk;oldNioBuffer = buf.tmpNioBuf;oldHandle = buf.handle;oldMemory = buf.memory;oldOffset = buf.offset;oldMaxLength = buf.maxLength;oldCache = buf.cache;// This does not touch buf's reader/writer indices// 為 buf 分配新的內存空間allocate(parent.threadCache(), buf, newCapacity);}int bytesToCopy;if (newCapacity > oldCapacity) {bytesToCopy = oldCapacity;} else {buf.trimIndicesToCapacity(newCapacity); // 如果新容量更小,調整讀寫指針bytesToCopy = newCapacity;}memoryCopy(oldMemory, oldOffset, buf, bytesToCopy); // 將舊內存數據拷貝到新內存free(oldChunk, oldNioBuffer, oldHandle, oldMaxLength, oldCache); // 釋放舊的內存塊}
// ...
reallocate 函數的核心作用是“搬家”:為 ByteBuf 申請一塊新的、符合 newCapacity 大小的內存,將舊內存中的有效數據復制過去,然后釋放舊的內存。這個過程確保了 ByteBuf 擴容或縮容時數據的完整性。
其主要步驟如下:
-
線程安全保障 : synchronized (buf) 會鎖定當前的 ByteBuf 實例,防止多個線程同時對同一個 ByteBuf 進行重分配,避免狀態錯亂。
-
保存舊內存信息 :記錄下當前 ByteBuf 的舊容量、所屬的 PoolChunk 、句柄 handle 、內存對象 memory 等所有與舊內存位置相關的信息。
-
分配新內存 :調用 allocate(parent.threadCache(), buf, newCapacity) 方法,從內存池中申請一塊新的內存。這個調用會更新 buf 對象內部的字段(如 chunk , handle , memory 等),使其指向新的內存位置。
-
數據拷貝 :調用 memoryCopy(...) 方法,將舊內存中的數據拷貝到新分配的內存中。拷貝的字節數是新舊容量中較小的那一個,以防止越界。
-
釋放舊內存 :調用 free(...) 方法,將之前保存的舊內存塊信息傳入,把舊內存歸還給內存池,以便后續復用。
HeapArena
?(堆內存)
lastDestroyedChunk
:?HeapArena
?會嘗試緩存最后一個被銷毀的?PoolChunk<byte[]>
。當需要創建新的?PoolChunk
?時,如果參數匹配,會復用這個緩存的?PoolChunk
,避免重新分配?byte[]
?數組的開銷。newByteArray(int size)
: 使用?PlatformDependent.allocateUninitializedArray(size)
?來分配字節數組,這可能比?new byte[size]
?更高效,因為它可能不會對數組內容進行零初始化(取決于 JVM 實現和?-XX:+AlwaysZeroTLAB
?等標志)。newChunk(...)
: 嘗試從?lastDestroyedChunk
?復用,否則創建新的?PoolChunk
,其內存是新分配的?byte[]
。newUnpooledChunk(...)
: 創建新的?PoolChunk
,其內存是新分配的?byte[]
。destroyChunk(...)
: 如果是池化的?PoolChunk
?并且?lastDestroyedChunk
?為空,則將其緩存起來。否則依賴 GC 回收?byte[]
。newByteBuf(...)
: 根據?PlatformDependent.hasUnsafe()
?的結果,創建?PooledUnsafeHeapByteBuf
?或?PooledHeapByteBuf
。memoryCopy(...)
: 使用?System.arraycopy
?進行內存拷貝。
HeapArena
是 Netty 高性能內存池在堆內存管理上的具體體現。它通過繼承 PoolArena
的通用分級管理框架(PoolChunkList
和 PoolSubpage
),并結合線程緩存(PoolThreadCache
)機制,實現了高效的內存分配與回收。
其針對堆內存的特有實現,如復用 PoolChunk
對象和使用 System.arraycopy
,進一步優化了性能,有效降低了 GC 壓力和內存碎片,是 Netty 實現高性能網絡通信的重要基石之一。
HeapArena
繼承自 PoolArena
,因此其核心結構與 PoolArena
保持一致,主要包括:
- ??
smallSubpagePools
??:PoolSubpage<byte[]>[]
數組,用于管理小規格內存(小于pageSize
)的分配。每個PoolSubpage
內部通過位圖(bitmap)來跟蹤更小內存塊(element)的分配狀態,實現了對小內存的高效利用。 - ??
PoolChunkList
鏈表??(qInit
、q000
、q025
、q050
、q075
、q100
):這些鏈表用于管理不同內存使用率區間的PoolChunk
。PoolChunk
是內存池的基本分配單元(默認為 16MB)。通過將PoolChunk
按使用率分組,可以快速找到合適的Chunk
進行內存分配,實現了分級管理。 - ??
SizeClasses
??:一個用于規格化請求容量的工具類,它將任意大小的內存請求映射到預定義的規格(size index),并提供相關的計算方法。 - ??鎖機制??:使用
ReentrantLock
來保證在多線程環境下對Arena
內部數據結構訪問的線程安全。
內存分配 (allocate)
HeapArena
作為 PoolArena
的子類,重寫了幾個關鍵的抽象方法,以適配堆內存的特性:
- ??
isDirect()
??:返回false
,表明它管理的是非直接內存(堆內存)。 - ??
newChunk(...)
??:創建一個新的PoolChunk<byte[]>
。值得注意的是,這里有一個優化:它會嘗試復用一個最近被銷毀的PoolChunk
(通過lastDestroyedChunk
字段)。如果復用失敗,則通過newByteArray(chunkSize)
創建一個新的byte[]
數組作為PoolChunk
的底層內存。
private final AtomicReference<PoolChunk<byte[]>> lastDestroyedChunk;protected PoolChunk<byte[]> newChunk(int pageSize, int maxPageIdx, int pageShifts, int chunkSize) {PoolChunk<byte[]> chunk = lastDestroyedChunk.getAndSet(null);if (chunk != null) {assert chunk.chunkSize == chunkSize &&chunk.pageSize == pageSize &&chunk.maxPageIdx == maxPageIdx &&chunk.pageShifts == pageShifts;return chunk; // The parameters are always the same, so it's fine to reuse a previously allocated chunk.}return new PoolChunk<byte[]>(this, null, null, newByteArray(chunkSize), pageSize, pageShifts, chunkSize, maxPageIdx);}
- ??
newUnpooledChunk(...)
??:創建一個用于大內存分配的非池化PoolChunk
,底層同樣是byte[]
。 - ??
newByteArray(...)
??:內部調用PlatformDependent.allocateUninitializedArray(size)
來分配byte[]
數組。使用allocateUninitializedArray
可以避免數組初始化時的額外開銷。 - ??
destroyChunk(...)
??:銷毀一個PoolChunk
。對于堆內存,銷毀操作實際上依賴于 GC。但為了性能,HeapArena
會嘗試緩存最后一個被銷毀的Chunk
(lastDestroyedChunk.set(chunk)
),以便在下次創建新Chunk
時可以復用。 - ??
newByteBuf(...)
??:根據PlatformDependent.hasUnsafe()
的結果,創建PooledUnsafeHeapByteBuf
或PooledHeapByteBuf
實例。 - ??
memoryCopy(...)
??:使用System.arraycopy
來實現內存復制,這是針對byte[]
數組最高效的方式。
DirectArena
??DirectArena?? 是 PoolArena
的一個靜態內部類,專門用于管理堆外內存(Direct Memory),其管理的內存類型是 java.nio.ByteBuffer
。它繼承了 PoolArena<ByteBuffer>
,復用了 PoolArena
中通用的內存池管理算法(如伙伴算法的變體、多層級的 PoolChunkList
管理等),但對直接內存的分配、釋放和操作等關鍵部分提供了專門的實現。
類的定義和構造函數
static final class DirectArena extends PoolArena<ByteBuffer> {DirectArena(PooledByteBufAllocator parent, SizeClasses sizeClass) {super(parent, sizeClass);}
}
- ??
extends PoolArena<ByteBuffer>
??
泛型參數<ByteBuffer>
表明這個 Arena 管理的底層內存是ByteBuffer
對象,與管理byte[]
的HeapArena
形成對比。 - ??
static final
??
static
意味著DirectArena
的實例不依賴于外部PoolArena
的實例;final
表示它不能被繼承。
內存塊的創建(newChunk
和 newUnpooledChunk
)
@Override
protected PoolChunk<ByteBuffer> newChunk(int pageSize, int maxPageIdx, int pageShifts, int chunkSize) {if (sizeClass.directMemoryCacheAlignment == 0) {CleanableDirectBuffer cleanableDirectBuffer = allocateDirect(chunkSize);return new PoolChunk<ByteBuffer>(this, cleanableDirectBuffer, memory, memory, pageSize, pageShifts, chunkSize, maxPageIdx);}CleanableDirectBuffer cleanableDirectBuffer = allocateDirect(chunkSize + sizeClass.directMemoryCacheAlignment);final ByteBuffer memory = PlatformDependent.alignDirectBuffer(base, sizeClass.directMemoryCacheAlignment);return new PoolChunk<ByteBuffer>(this, cleanableDirectBuffer, base, memory, pageSize, pageShifts, chunkSize, maxPageIdx);
}
- ??核心功能??
與HeapArena
使用new byte[chunkSize]
不同,DirectArena
通過PlatformDependent.allocateDirect(capacity)
分配堆外內存(通常調用ByteBuffer.allocateDirect()
)。 - ??內存對齊(
directMemoryCacheAlignment
)??
若directMemoryCacheAlignment > 0
,會分配稍大的內存并通過PlatformDependent.alignDirectBuffer()
對齊地址,優化 CPU 緩存行利用,防止偽共享(False Sharing)。 - ??
CleanableDirectBuffer
??
包裝分配的內存,利用 Java 9+ 的Cleaner
(或舊版類似機制)確保PoolChunk
和ByteBuffer
被垃圾回收時,底層堆外內存可靠釋放,防止泄漏。
內存塊的銷毀和創建
@Override
protected void destroyChunk(PoolChunk<ByteBuffer> chunk) {chunk.cleanable.clean();
}
- ??與
HeapArena
的區別??
HeapArena
會緩存最后銷毀的PoolChunk
以供復用(lastDestroyedChunk
),而DirectArena
直接調用chunk.cleanable.clean()
立即釋放內存。
理解為什么可以“銷毀”,我們需要知道 destroyChunk 是在什么條件下被觸發的。
- 當一個 PoolChunk 內的所有內存都被釋放后,它的使用率會降為 0% ( usage() == 0 )。此時, PoolChunkList 的 free 方法會嘗試將這個完全空閑的 chunk 移動到前一個 PoolChunkList (即使用率更低的 List )。
- 然而,對于管理著 0%-25% 使用率的 qInit 這個 PoolChunkList 來說,它的 prevList 是 null 。當它試圖移動一個使用率為 0 的 chunk 時, move0 方法會因為 prevList == null 而返回 false 。這個 false 返回值會一路傳遞,最終導致 PoolArena 調用 destroyChunk 。
因此 當destroyChunk 被調用,意味著這個 PoolChunk 已經完全變空了 。它不再服務于任何內存分配。
ByteBuf
的創建(newByteBuf
)
@Override
protected PooledByteBuf<ByteBuffer> newByteBuf(int maxCapacity) {if (HAS_UNSAFE) {return PooledUnsafeDirectByteBuf.newInstance(maxCapacity);} else {return PooledDirectByteBuf.newInstance(maxCapacity);}
}
- ??創建策略??
根據PlatformDependent.hasUnsafe()
結果選擇實現:- ??
PooledUnsafeDirectByteBuf
??(支持sun.misc.Unsafe
):使用Unsafe
API 直接操作內存地址,性能最高。 - ??
PooledDirectByteBuf
??(不支持Unsafe
):回退到標準ByteBuffer
API(get
/put
),兼容性更好。
- ??
內存復制(memoryCopy
)
@Override
protected void memoryCopy(ByteBuffer src, int srcOffset, PooledByteBuf<ByteBuffer> dstBuf, int length) {if (HAS_UNSAFE) {PlatformDependent.copyMemory(PlatformDependent.directBufferAddress(src) + srcOffset,PlatformDependent.directBufferAddress(dstBuf.memory) + dstBuf.offset, length);} else {src.position(srcOffset).limit(srcOffset + length);dst.position(dstBuf.offset);dst.put(src);}
}
- ??高性能復制??
- 支持
Unsafe
時調用PlatformDependent.copyMemory
(底層為Unsafe.copyMemory
),實現高效內存塊復制。 - 否則回退到
ByteBuffer.put(ByteBuffer)
的標準方法。
- 支持
度量信息 (Metrics)
PoolArena
?實現了?PoolArenaMetric
?接口,提供了大量關于內存池狀態的度量信息,例如:
numThreadCaches()
: 使用此 Arena 的線程緩存數量。numSmallSubpages()
,?numChunkLists()
: Subpage 和 ChunkList 的數量。smallSubpages()
,?chunkLists()
: 返回 Subpage 和 ChunkList 的度量信息列表。numAllocations()
,?numDeallocations()
: 總的分配和釋放次數。numSmallAllocations()
,?numNormalAllocations()
,?numHugeAllocations()
: 不同類型的分配次數。numChunkAllocations()
,?numChunkDeallocations()
: Chunk 的分配和釋放次數。numActiveAllocations()
,?numActiveSmallAllocations()
, etc.: 當前活躍的分配數量。numActiveBytes()
: 當前活躍的總字節數。numPinnedBytes()
: 當前被固定的字節數(用于直接 I/O 等)。
這些方法大多通過讀取內部的計數器或遍歷?chunkListMetrics
?來獲取數據。訪問某些計數器(如?allocationsNormal
)時會加鎖。
總結
PoolArena
?是 Netty jemalloc 風格內存池的核心,它通過管理?PoolChunk
?和?PoolSubpage
?來高效地分配和釋放內存。
- 分級管理: 將內存請求分為 Small, Normal, Huge 三種類型,采用不同的分配策略。
- Chunk 列表: 使用多個?
PoolChunkList
?(qInit, q000, q025, q050, q075, q100) 根據?PoolChunk
?的使用率對其進行組織,優化分配查找。 - Subpage 池: 對 Small 類型的分配,使用?
PoolSubpage
?進一步細化內存管理,減少碎片。 - 線程緩存: 與?
PoolThreadCache
?配合,為每個線程提供本地緩存,極大減少了對?PoolArena
?全局鎖的競爭。 - 同步: 使用?
ReentrantLock
?保護 Arena 級別的共享數據,使用?PoolSubpage
?自身的鎖保護其內部鏈表,使用?synchronized(buf)
?保護?reallocate
?操作。 - 堆外/堆內支持: 通過?
HeapArena
?和?DirectArena
?子類分別支持堆內存和直接內存的池化。 - 度量: 提供豐富的度量信息,方便監控和調優。
PoolArena
?的設計體現了 Netty 在高性能網絡編程中對內存管理的極致追求,通過精細化的管理和多層次的緩存來提高內存分配效率并減少GC壓力。