Netty原始碼分析(七) PoolChunk
在分析原始碼之前,我們先來了解一下Netty的記憶體管理機制。我們知道,jvm是自動管理記憶體的,這帶來了一些好處,在分配記憶體的時候可以方便管理,也帶來了一些問題。jvm每次分配記憶體的時候,都是先要去堆上申請記憶體空間進行分配,這就帶來了很大的效能上的開銷。當然,也可以使用堆外記憶體,Netty就用了堆外記憶體,但是記憶體的申請和釋放,依然需要效能的開銷。所以Netty實現了記憶體池來管理記憶體的申請和使用,提高了記憶體使用的效率。
PoolChunk就是Netty的記憶體管理的一種實現。Netty一次向系統申請16M的連續記憶體空間,這塊記憶體通過PoolChunk物件包裝,為了更細粒度的管理它,進一步的把這16M記憶體分成了2048個頁(pageSize=8k)。頁作為Netty記憶體管理的最基本的單位 ,所有的記憶體分配首先必須申請一塊空閒頁。Ps: 這裡可能有一個疑問,如果申請1Byte的空間就分配一個頁是不是太浪費空間,在Netty中Page還會被細化用於專門處理小於4096Byte的空間申請 那麼這些Page需要通過某種資料結構跟演算法管理起來。
先來看看PoolChunk有哪些屬性
/**
* 所屬 Arena 物件
*/
final PoolArena<T> arena;
/**
* 記憶體空間。
*
* @see PooledByteBuf#memory
*/
final T memory;
/**
* 是否非池化
*
* @see #PoolChunk(PoolArena, Object, int, int) 非池化。當申請的記憶體大小為 Huge 型別時,建立一整塊 Chunk ,並且不拆分成若干 Page
* @see #PoolChunk(PoolArena, Object, int, int, int, int, int) 池化
*/
final boolean unpooled;
final int offset;
/**
* 分配資訊滿二叉樹
*
* index 為節點編號
*/
private final byte[] memoryMap;
/**
* 高度資訊滿二叉樹
*
* index 為節點編號
*/
private final byte[] depthMap;
/**
* PoolSubpage 陣列
*/
private final PoolSubpage<T>[] subpages;
/**
* 判斷分配請求記憶體是否為 Tiny/Small ,即分配 Subpage 記憶體塊。
*
* Used to determine if the requested capacity is equal to or greater than pageSize.
*/
private final int subpageOverflowMask;
/**
* Page 大小,預設 8KB = 8192B
*/
private final int pageSize;
/**
* 從 1 開始左移到 {@link #pageSize} 的位數。預設 13 ,1 << 13 = 8192 。
*
* 具體用途,見 {@link #allocateRun(int)} 方法,計算指定容量所在滿二叉樹的層級。
*/
private final int pageShifts;
/**
* 滿二叉樹的高度。預設為 11 。
*/
private final int maxOrder;
/**
* Chunk 記憶體塊佔用大小。預設為 16M = 16 * 1024 。
*/
private final int chunkSize;
/**
* log2 {@link #chunkSize} 的結果。預設為 log2( 16M ) = 24 。
*/
private final int log2ChunkSize;
/**
* 可分配 {@link #subpages} 的數量,即陣列大小。預設為 1 << maxOrder = 1 << 11 = 2048 。
*/
private final int maxSubpageAllocs;
Netty採用完全二叉樹進行管理,樹中每個葉子節點表示一個Page,即樹高為12,中間節點表示頁節點的持有者。有了上面的資料結構,那麼頁的申請跟釋放就非常簡單了,只需要從根節點一路遍歷找到可用的節點即可。主要來看看PoolChunk是怎麼分配記憶體的。
long allocate(int normCapacity) {
// 大於等於 Page 大小,分配 Page 記憶體塊
if ((normCapacity & subpageOverflowMask) != 0) { // >= pageSize
return allocateRun(normCapacity);
// 小於 Page 大小,分配 Subpage 記憶體塊
} else {
return allocateSubpage(normCapacity);
}
}
private long allocateRun(int normCapacity) {
// 獲得層級
int d = maxOrder - (log2(normCapacity) - pageShifts);
// 獲得節點
int id = allocateNode(d);
// 未獲得到節點,直接返回
if (id < 0) {
return id;
}
// 減少剩餘可用位元組數
freeBytes -= runLength(id);
return id;
}
private long allocateSubpage(int normCapacity) {
// 獲得對應記憶體規格的 Subpage 雙向連結串列的 head 節點
// Obtain the head of the PoolSubPage pool that is owned by the PoolArena and synchronize on it.
// This is need as we may add it back and so alter the linked-list structure.
PoolSubpage<T> head = arena.findSubpagePoolHead(normCapacity);
// 加鎖,分配過程會修改雙向連結串列的結構,會存在多執行緒的情況。
synchronized (head) {
// 獲得最底層的一個節點。Subpage 只能使用二叉樹的最底層的節點。
int d = maxOrder; // subpages are only be allocated from pages i.e., leaves
int id = allocateNode(d);
// 獲取失敗,直接返回
if (id < 0) {
return id;
}
final PoolSubpage<T>[] subpages = this.subpages;
final int pageSize = this.pageSize;
// 減少剩餘可用位元組數
freeBytes -= pageSize;
// 獲得節點對應的 subpages 陣列的編號
int subpageIdx = subpageIdx(id);
// 獲得節點對應的 subpages 陣列的 PoolSubpage 物件
PoolSubpage<T> subpage = subpages[subpageIdx];
// 初始化 PoolSubpage 物件
if (subpage == null) { // 不存在,則進行建立 PoolSubpage 物件
subpage = new PoolSubpage<T>(head, this, id, runOffset(id), pageSize, normCapacity);
subpages[subpageIdx] = subpage;
} else { // 存在,則重新初始化 PoolSubpage 物件
subpage.init(head, normCapacity);
}
// 分配 PoolSubpage 記憶體塊
return subpage.allocate();
}
}
Netty的記憶體按大小分為tiny,small,normal,而型別上可以分為PoolChunk,PoolSubpage,小於4096大小的記憶體就被分成PoolSubpage。Netty就是這樣實現了對記憶體的管理。
PoolChunk就分析到這裡了。
相關文章
- Netty原始碼分析----PoolChunkNetty原始碼
- Netty原始碼解析 -- PoolChunk實現原理Netty原始碼
- Netty原始碼分析(七):初識ChannelPipelineNetty原始碼
- Netty原始碼解析 -- PoolChunk實現原理(jemalloc 3的演算法)Netty原始碼演算法
- Netty原始碼分析--Channel註冊&繫結埠(下)(七)Netty原始碼
- Netty Channel原始碼分析Netty原始碼
- netty : NioEventLoopGroup 原始碼分析NettyOOP原始碼
- Netty原始碼分析(一):Netty總覽Netty原始碼
- Netty 原始碼分析系列(一)Netty 概述Netty原始碼
- Netty原始碼分析-- FastThreadLocal分析(十)Netty原始碼ASTthread
- Netty 原始碼分析-目錄Netty原始碼
- Netty Pipeline原始碼分析(2)Netty原始碼
- Netty原始碼分析之LengthFieldBasedFrameDecoderNetty原始碼
- Netty原始碼分析(五):EventLoopNetty原始碼OOP
- Netty原始碼分析(四):EventLoopGroupNetty原始碼OOP
- Netty原始碼分析--NIO(一)Netty原始碼
- Netty Pipeline原始碼分析(1)Netty原始碼
- netty原始碼分析之pipeline(二)Netty原始碼
- netty原始碼分析之pipeline(一)Netty原始碼
- Netty4.x原始碼分析Netty原始碼
- Netty原始碼分析--建立Channel(三)Netty原始碼
- Netty原始碼分析--Reactor模型(二)Netty原始碼React模型
- Netty 原始碼分析系列(二)Netty 架構設計Netty原始碼架構
- Netty NioEventLoop 建立過程原始碼分析NettyOOP原始碼
- Netty中NioEventLoopGroup的建立原始碼分析NettyOOP原始碼
- Netty啟動流程及原始碼分析Netty原始碼
- 深入淺出Netty記憶體管理 PoolChunkNetty記憶體
- Netty NioEventLoop 啟動過程原始碼分析NettyOOP原始碼
- Netty原始碼分析--Channel註冊(中)(六)Netty原始碼
- Netty原始碼分析--Channel註冊(上)(五)Netty原始碼
- Netty原始碼分析之ByteBuf引用計數Netty原始碼
- Netty原始碼分析之自定義編解碼器Netty原始碼
- Netty原始碼分析(三):客戶端啟動Netty原始碼客戶端
- Netty原始碼分析(二):服務端啟動Netty原始碼服務端
- Netty的原始碼分析和業務場景Netty原始碼
- Netty 原始碼分析之拆包器的奧祕Netty原始碼
- netty原始碼分析之新連線接入全解析Netty原始碼
- Netty原始碼分析之NioEventLoop(三)—NioEventLoop的執行Netty原始碼OOP