## 8.3.1 內存分配
netty進行分配時,主要流程比較簡單,首先從對象池獲取ByteBuf,之后從線程本地緩存MemoryRegionCache中查找內存頁,再從Arena的內存池中查找,最后查找Chunk,分配SubPage,最后初始化bytebuf。
1. 從線程的本地緩存中獲取PoolThreadCache對象,如果沒有,則選擇使用空間最少的Arena創建PoolThreadCache實例并保存至線程本地
2. 使用PoolThreadCache的Arena從對象池中獲取ByteBuf,對象池中默認沒有釋放的對象,會創建新對象。Arena根據HAS_UNSAFE判斷是PooledUnsafeHeapByteBuf還是PooledHeapByteBuf進行相應處理。
3. 根據請求的內存大小,判斷其規格:tiny/small/normal/huge,
* 若大小為tiny(0,512),從本地緩存的PoolThreadCache的MemoryRegionCache中查看是否有釋放后的內存可以重用,若有則初始化PooledByteBuf;本地緩存池中沒有可重用內存,先根據大小定位到在Arena的tinySubpagePools的位置idx,然后在其中查找可用的PoolSubpage,如果找到內存頁,則使用內存頁的chunk的初始化PoolSubpage。
* 若大小為small[512,pagesize],邏輯與tiny類似,只是尋找緩存所在Arena中屬性有所不同。
* tiny和small在MemoryRegionCache和tinySubpagePools/smallSubpagePools中未找到可用分配的內存頁則會調用allocateNormal尋找chunk
* 若大小為normal(pagesize,chunksize],會先從MemoryRegionCache中查找可用的回收后的緩存,如果未找到則會調用allocateNormal尋找chunk
4. 尋找chunk時,先從q050->q025->q000->qInit->q075查找可用的chunk,如果沒有找到,會創建PoolChunk對象的實例,創建chunk時會分配實際內存(heap使用byte[],direct使用ByteBuffer.allocateDirect)。找到chunk后,在chunk中查找或創建內存頁,最后返回一個Handle。Handle是一個long型整數,記錄了chunk和內部的偏移。
5. chunk尋找內存頁的過程:根據請求大小計算idx,并找到tinySubpagePools或smallSubpagePools中idx位置的鏈表head;再根據大小計算在chunk的memoryMap葉子節點的index,如果chunk的subpages數組中index位置為空,說明沒有創建PoolSubpage,創建新的內存頁之后,并將其加入到chunk的subpages數組。最后修改subpage中的bitmap,講該內存頁加入到tinySubpagePools或smallSubpagePools對應位置的鏈表中。最后計算出分配出的內存的Handle,`0x4000000000000000L | (long) bitmapIdx << 32 | memoryMapIdx;` 內部保存了所分配的內存在chunk中的內存頁位置memoryMapIdx和在內存頁中的位置bitmapIdx
6. 獲得Handle之后,對第2步中獲取的ByteBuf進行初始化。
7. 如果chunk是新創建的,還需要加入到Arena的chunklist中。
## 8.3.2 內存釋放
調用ByteBuf的release方法可以釋放內存,主要分為兩步:使用Arena釋放ByteBuf,將ByteBuf回收到對象池中。
Arena釋放ByteBuf時,如果線程本地PoolThreadCache不為空,查找PoolThreadCache的Caches數組中對應的MemoryRegionCache,將chunk和Handle加入到MemoryRegionCache的queue中。