PoolArena
是Netty內存池中的一個核心容器,它的主要做用是對建立的一系列的PoolChunk
和PoolSubpage
進行管理,根據申請的不一樣內存大小將最終的申請動做委託給這兩個子容器進行管理。總體上,PoolArena
管理的內存有直接內存和堆內存兩種方式,其是經過子類繼承的方式來實現對不一樣類型的內存的申請與釋放的。本文首先會對PoolArena
的總體結構進行介紹,而後會介紹其主要屬性,接着會從源碼的角度對PoolArena
申請和釋放內存的過程進行介紹。java
在總體上,PoolArena是對內存申請和釋放的一個抽象,其有兩個子類,結構以下圖所示:數組
這裏DirectArena
和HeapArena
是PoolArena
對不一樣類型的內存申請和釋放進行管理的兩個具體的實現,內存的處理工做主要仍是在PoolArena中。從結構上來看,PoolArena
中主要包含三部分子內存池:tinySubpagePools,smallSubpagePools和一系列的PoolChunkList。tinySubpagePools和smallSubpagePools都是PoolSubpage的數組,數組長度分別爲32和4;PoolChunkList則主要是一個容器,其內部能夠保存一系列的PoolChunk對象,而且,Netty會根據內存使用率的不一樣,將PoolChunkList分爲不一樣等級的容器。以下是PoolArena
在初始狀態時的結構示意圖:緩存
關於PoolArena
的結構,主要有以下幾點須要說明:多線程
PoolArena中有很是多的屬性值,用於對PoolSubpage、PookChunk和PoolChunkList進行控制。在閱讀源碼時,若是可以理解這些屬性值的做用,將會極大的加深對Netty內存池的理解。咱們這裏對PoolArena的主要屬性進行介紹:併發
// 該參數指定了tinySubpagePools數組的長度,因爲tinySubpagePools每個元素的內存塊差值爲16, // 於是數組長度是512/16,也即這裏的512 >>> 4 static final int numTinySubpagePools = 512 >>> 4; // 記錄了PooledByteBufAllocator的引用 final PooledByteBufAllocator parent; // PoolChunk底層是一個平衡二叉樹,該參數指定了該二叉樹的深度 private final int maxOrder; // 該參數指定了PoolChunk中每個葉節點所指代的內存塊的大小 final int pageSize; // 指定了葉節點大小8KB是2的多少次冪,默認爲13,該字段的主要做用是,在計算目標內存屬於二叉樹的 // 第幾層的時候,能夠藉助於其內存大小相對於pageShifts的差值,從而快速計算其所在層數 final int pageShifts; // 指定了PoolChunk的初始大小,默認爲16M final int chunkSize; // 因爲PoolSubpage的大小爲8KB=8196,於是該字段的值爲 // -8192=>=> 1111 1111 1111 1111 1110 0000 0000 0000 // 這樣在判斷目標內存是否小於8KB時,只須要將目標內存與該數字進行與操做,只要操做結果等於0, // 就說明目標內存是小於8KB的,這樣就能夠判斷其是應該首先在tinySubpagePools或smallSubpagePools // 中進行內存申請 final int subpageOverflowMask; // 該參數指定了smallSubpagePools數組的長度,默認爲4 final int numSmallSubpagePools; // 指定了直接內存緩存的校準值 final int directMemoryCacheAlignment; // 指定了直接內存緩存校準值的判斷變量 final int directMemoryCacheAlignmentMask; // 存儲內存塊小於512byte的PoolSubpage數組,該數組是分層次的,好比其第1層只用於大小爲16byte的 // 內存塊的申請,第2層只用於大小爲32byte的內存塊的申請,……,第31層只用於大小爲496byte的內存塊的申請 private final PoolSubpage<T>[] tinySubpagePools; // 用於大小在512byte~8KB內存的申請,該數組長度爲4,所申請的內存塊大小爲512byte、1024byte、 // 2048byte和4096byte。 private final PoolSubpage<T>[] smallSubpagePools; // 用戶維護使用率在50~100%的PoolChunk private final PoolChunkList<T> q050; // 用戶維護使用率在25~75%的PoolChunk private final PoolChunkList<T> q025; // 用戶維護使用率在1~50%的PoolChunk private final PoolChunkList<T> q000; // 用戶維護使用率在0~25%的PoolChunk private final PoolChunkList<T> qInit; // 用戶維護使用率在75~100%的PoolChunk private final PoolChunkList<T> q075; // 用戶維護使用率爲100%的PoolChunk private final PoolChunkList<T> q100; // 記錄了當前PoolArena已經被多少個線程使用了,在每個線程申請新內存的時候,其會找到使用最少的那個 // PoolArena進行內存的申請,這樣能夠減小線程之間的競爭 final AtomicInteger numThreadCaches = new AtomicInteger();
PoolArena對內存申請的控制,主要是按照前面的描述,對其流程進行控制。關於PoolChunk和PoolSubpage對內存申請和釋放的控制,讀者能夠閱讀本人前面的文章:Netty內存池之PoolChunk原理詳解和Netty內存池之PoolSubpage詳解。這裏咱們主要在PoolArena層面上對內存的申請進行講解,以下是其allocate()方法的源碼:this
PooledByteBuf<T> allocate(PoolThreadCache cache, int reqCapacity, int maxCapacity) { // 這裏newByteBuf()方法將會建立一個PooledByteBuf對象,可是該對象是未經初始化的, // 也就是說其內部的ByteBuffer和readerIndex,writerIndex等參數都是默認值 PooledByteBuf<T> buf = newByteBuf(maxCapacity); // 使用對應的方式爲建立的ByteBuf初始化相關內存數據,咱們這裏是以DirectArena進行講解,於是這裏 // 是經過其allocate()方法申請內存 allocate(cache, buf, reqCapacity); return buf; }
上述方法主要是一個入口方法,首先建立一個屬性都是默認值的ByteBuf對象,而後將真正的申請動做交由allocate()方法進行:.net
private void allocate(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity) { // 這裏normalizeCapacity()方法的主要做用是對目標容量進行規整操做,主要規則以下: // 1. 若是目標容量小於16字節,則返回16; // 2. 若是目標容量大於16字節,小於512字節,則以16字節爲單位,返回大於目標字節數的第一個16字節的倍數。 // 好比申請的100字節,那麼大於100的16的倍數是112,於是返回112個字節 // 3. 若是目標容量大於512字節,則返回大於目標容量的第一個2的指數冪。 // 好比申請的1000字節,那麼返回的將是1024 final int normCapacity = normalizeCapacity(reqCapacity); // 判斷目標容量是否小於8KB,小於8KB則使用tiny或small的方式申請內存 if (isTinyOrSmall(normCapacity)) { int tableIdx; PoolSubpage<T>[] table; boolean tiny = isTiny(normCapacity); // 判斷目標容量是否小於512字節,小於512字節的爲tiny類型的 if (tiny) { // 這裏首先從當前線程的緩存中嘗試申請內存,若是申請到了,則直接返回,該方法中會使用申請到的 // 內存對ByteBuf對象進行初始化 if (cache.allocateTiny(this, buf, reqCapacity, normCapacity)) { return; } // 若是沒法從當前線程緩存中申請到內存,則嘗試從tinySubpagePools中申請,這裏tinyIdx()方法 // 就是計算目標內存是在tinySubpagePools數組中的第幾號元素中的 tableIdx = tinyIdx(normCapacity); table = tinySubpagePools; } else { // 若是目標內存在512byte~8KB之間,則嘗試從smallSubpagePools中申請內存。這裏首先從 // 當前線程的緩存中申請small級別的內存,若是申請到了,則直接返回 if (cache.allocateSmall(this, buf, reqCapacity, normCapacity)) { return; } // 若是沒法從當前線程的緩存中申請到small級別的內存,則嘗試從smallSubpagePools中申請。 // 這裏smallIdx()方法就是計算目標內存塊是在smallSubpagePools中的第幾號元素中的 tableIdx = smallIdx(normCapacity); table = smallSubpagePools; } // 獲取目標元素的頭結點 final PoolSubpage<T> head = table[tableIdx]; // 這裏須要注意的是,因爲對head進行了加鎖,而在同步代碼塊中判斷了s != head, // 也就是說PoolSubpage鏈表中是存在未使用的PoolSubpage的,由於若是該節點已經用完了, // 其是會被移除當前鏈表的。也就是說只要s != head,那麼這裏的allocate()方法 // 就必定可以申請到所須要的內存塊 synchronized (head) { final PoolSubpage<T> s = head.next; // s != head就證實當前PoolSubpage鏈表中存在可用的PoolSubpage,而且必定可以申請到內存, // 由於已經耗盡的PoolSubpage是會從鏈表中移除的 if (s != head) { // 從PoolSubpage中申請內存 long handle = s.allocate(); // 經過申請的內存對ByteBuf進行初始化 s.chunk.initBufWithSubpage(buf, null, handle, reqCapacity); // 對tiny類型的申請數進行更新 incTinySmallAllocation(tiny); return; } } synchronized (this) { // 走到這裏,說明目標PoolSubpage鏈表中沒法申請到目標內存塊,於是就嘗試從PoolChunk中申請 allocateNormal(buf, reqCapacity, normCapacity); } // 對tiny類型的申請數進行更新 incTinySmallAllocation(tiny); return; } // 走到這裏說明目標內存是大於8KB的,那麼就判斷目標內存是否大於16M,若是大於16M, // 則不使用內存池對其進行管理,若是小於16M,則到PoolChunkList中進行內存申請 if (normCapacity <= chunkSize) { // 小於16M,首先到當前線程的緩存中申請,若是申請到了則直接返回,若是沒有申請到, // 則到PoolChunkList中進行申請 if (cache.allocateNormal(this, buf, reqCapacity, normCapacity)) { return; } synchronized (this) { // 在當前線程的緩存中沒法申請到足夠的內存,於是嘗試到PoolChunkList中申請內存 allocateNormal(buf, reqCapacity, normCapacity); ++allocationsNormal; } } else { // 對於大於16M的內存,Netty不會對其進行維護,而是直接申請,而後返回給用戶使用 allocateHuge(buf, reqCapacity); } }
上述代碼就是PoolArena申請目標內存塊的主要流程,首先會判斷目標內存是在哪一個內存層級的,好比tiny、small或者normal,而後根據目標層級的分配方式對目標內存進行擴容。接着首先會嘗試從當前線程的緩存中申請目標內存,若是可以申請到,則直接返回,若是不能申請到,則在當前層級中申請。對於tiny和small層級的內存申請,若是沒法申請到,則會將申請動做交由PoolChunkList進行。這裏咱們主要看一下PoolArena是如何在PoolChunkList中申請內存的,以下是allocateNormal()的源碼:線程
private void allocateNormal(PooledByteBuf<T> buf, int reqCapacity, int normCapacity) { // 將申請動做按照q050->q025->q000->qInit->q075的順序依次交由各個PoolChunkList進行處理, // 若是在對應的PoolChunkList中申請到了內存,則直接返回 if (q050.allocate(buf, reqCapacity, normCapacity) || q025.allocate(buf, reqCapacity, normCapacity) || q000.allocate(buf, reqCapacity, normCapacity) || qInit.allocate(buf, reqCapacity, normCapacity) || q075.allocate(buf, reqCapacity, normCapacity)) { return; } // 因爲在目標PoolChunkList中沒法申請到內存,於是這裏直接建立一個PoolChunk, // 而後在該PoolChunk中申請目標內存,最後將該PoolChunk添加到qInit中 PoolChunk<T> c = newChunk(pageSize, maxOrder, pageShifts, chunkSize); boolean success = c.allocate(buf, reqCapacity, normCapacity); qInit.add(c); }
這裏申請過程比較簡單,首先是按照必定的順序分別在各個PoolChunkList中申請內存,若是申請到了,則直接返回,若是沒申請到,則建立一個PoolChunk進行申請。這裏須要說明的是,在PoolChunkList中申請內存時,本質上仍是將申請動做交由其內部的PoolChunk進行申請,若是申請到了,其還會判斷當前PoolChunk的內存使用率是否超過了當前PoolChunkList的閾值,若是超過了,則會將其移動到下一PoolChunkList中。設計
對於內存的釋放,PoolArena主要是分爲兩種狀況,即池化和非池化,若是是非池化,則會直接銷燬目標內存塊,若是是池化的,則會將其添加到當前線程的緩存中。以下是free()方法的源碼:指針
void free(PoolChunk<T> chunk, ByteBuffer nioBuffer, long handle, int normCapacity, PoolThreadCache cache) { // 若是是非池化的,則直接銷燬目標內存塊,而且更新相關的數據 if (chunk.unpooled) { int size = chunk.chunkSize(); destroyChunk(chunk); activeBytesHuge.add(-size); deallocationsHuge.increment(); } else { // 若是是池化的,首先判斷其是哪一種類型的,即tiny,small或者normal, // 而後將其交由當前線程的緩存進行處理,若是添加成功,則直接返回 SizeClass sizeClass = sizeClass(normCapacity); if (cache != null && cache.add(this, chunk, nioBuffer, handle, normCapacity, sizeClass)) { return; } // 若是當前線程的緩存已滿,則將目標內存塊返還給公共內存塊進行處理 freeChunk(chunk, handle, sizeClass, nioBuffer); } }
本文首先對PoolArena的總體結構進行了講解,而且講解了PoolArena是如何控制內存申請流轉的,而後介紹了PoolArena中各個屬性的做用,最後從源碼的角度講解了PoolArena是如何控制內存的申請的。