看完這篇還不清楚Netty的內存管理,那我就哭了!

說明

在學習Netty的時候,ByteBuf隨處可見,可是如何高效分配ByteBuf仍是很複雜的,Netty的池化內存分配這塊仍是比較難的,不少人學習過,看過可是仍是雲裏霧裏的,本篇文章就是主要來說解:**Netty分配池化的堆外內存的細節,**期待可讓你明白!!!html

因爲爲了更好的表達,文章中的圖我最少畫了6小時,畫的不熟悉,而且也強調一些細節上。java

因爲該源碼中涉及到大量的二進制操做,建議看看我以前寫的2篇二進制文章:java二進制相關基礎二進制實戰技巧git

ByteBuf重要性

ByteBuf在Netty中一直存在,讀寫必備!ByteBuf是Netty的數據容器,高效分配ByteBuf相當重要!github

在這裏插入圖片描述

Netty從socket讀取數據。redis

在這裏插入圖片描述
Netty準備把數據寫到socket中去。

在這裏插入圖片描述

經過這裏咱們就能夠看到,再把數據寫socket的以前會判斷是不是堆外內存,若是不是會構造一個directbuffer對象的,細節代碼以下:數據庫

if (msg instanceof ByteBuf) {
            ByteBuf buf = (ByteBuf) msg;
            if (buf.isDirect()) {
                return msg;
            }

            return newDirectBuffer(buf);
        }
複製代碼

在這裏插入圖片描述

因此本篇文章就是主要來說解:**Netty分配池化的堆外內存的細節,**其實分配堆內存的細節不少也是相似的。api

備註: 爲何不是堆外內存還要轉堆外內存,爲何加這個判斷,我以前也不理解,突然有天和滌生大佬討論,討論討論就清晰了,後續有空寫篇。數組

總覽

本次主要討論的是關於池化內存的分配,PooledByteBufAllocator就是netty分配池化內存的操做入口。緩存

其提供對外經常使用操做api:bash

在這裏插入圖片描述

Netty在發送數據的時候會判斷是不是堆外內存,若是不是會進行封裝的:

在這裏插入圖片描述

全部這裏咱們以**分配池化的堆外內存爲例,進行本文說明。**池化的堆內存分配其實流程都差很少的。

下面咱們來看看分配示例demo:

public static void main(String[] args) {
    ByteBufAllocator alloc = PooledByteBufAllocator.DEFAULT;

    //tiny規格內存分配 會變成大於等於16的整數倍的數:這裏254 會規格化爲256
    ByteBuf byteBuf = alloc.directBuffer(254);

	//讀寫bytebuf
    byteBuf.writeInt(126);
    System.out.println(byteBuf.readInt());
    
    //很重要,內存釋放
    byteBuf.release();
}
複製代碼

後續咱們都會根據這段簡單的demo進行分析。

操做入口類

PooledByteBufAllocator的初始化:

進去以後能夠看到核心類的一初始化操做:

在這裏插入圖片描述

在這裏插入圖片描述

在這裏插入圖片描述

分配理論是jemalloc,能夠理解爲java版本的jemalloc實現。

PoolThreadCache

在這裏插入圖片描述

經過上圖能夠清晰的瞭解到PoolThreadCache的主要數據結構。

開始的時候,這些Cache裏面都是沒有值的,只有在調用free釋放的時候(在後續釋放內存中會講解),纔會把以前分配的內存大小放到該cache的queue裏面,其實每次分配的時候都是先看看是否緩存裏面有,若是有直接返回,沒有則進行正常的分配流程(內存分配會講解)。

咱們來看看PoolArena directArena內容:

下面咱們來看看PoolArena結構。

PoolArena

在這裏插入圖片描述

經過下圖能夠清晰的瞭解到PoolArena的主要數據結構。

在這裏插入圖片描述

在PoolArena裏面涉及到PoolChunkList和PoolSubpage對應的結構有PoolChunk和PoolSubpage,咱們來詳細的看看這2塊內容。

PoolChunk

第一次的時候,PoolChunkList、PoolSubpage都是默認值,須要新增一個Chunk,默認一個Chunk是16M。內部會結構是徹底二叉樹一共有4096個節點,有2048個葉子節點(每一個葉子節點大小爲一個page,就是8k),非葉子節點的內存大小等於左子樹內存大小加上右子樹內存大小。

徹底二叉樹結構以下:

在這裏插入圖片描述

這顆徹底二叉樹在java中是使用數組來進行表示的。

惟一須要注意的是,下標是從1開始而不是0.

在這裏插入圖片描述

depthMap的值初始化後再也不改變,memoryMap的值則隨着節點分配而改變。

在這裏插入圖片描述

這個值太多就不都截圖了,就是把上面那顆徹底二叉樹用數組表示了而已,只是值存的不是節點的下標而是存的樹的深度而已。

depthMap數組值爲0表示能夠分配16M空間,若是爲1 表示能夠分配8M,,若是爲2表示嗯能夠分配4M,若是爲3表示能夠分配2M ……………………若是爲11表示能夠分配8k空間。

若是該節點已經分配完成,就設置爲12便可。

怎麼肯定須要分配的大小在深度是多少?

若是須要分配的內存規格化以後,是小於8k,那麼在8k上面分配便可(即深度爲11)。

若是爲8k或者大於8k那麼經過下面代碼就能夠定位到深度了:

int d = maxOrder - (log2(normCapacity) - pageShifts);
複製代碼

知道深度以後,怎麼進行定位到那個節點呢???

在這裏插入圖片描述

找到該節點以後,先把該節點顯示佔用,在更新起父節點父節點的父………………以下:

在這裏插入圖片描述

SubpagePool

在這裏插入圖片描述

上面的圖就是關於SubpagePool的內存結構了。咱們在分配page的時候,根據memoryMap對於的值就知道是否被分配了,那麼若是是subpagePool呢?

subpagePool分爲2類:tinySubpagePools和smallSubpagePools,大小對於也對於上面的圖裏面了,每類都是固定大小的,若是分配256b的大小,那麼一個page就是8k,8*1024/256 = 32塊。那麼怎麼怎麼表示每一個還被分配了呢?

private final long[] bitmap;
複製代碼

因爲一個long佔64位,咱們這裏僅僅是須要表示32個,因此使用一個long便可了,二進制每位 1表示已經使用了,0表示還未使用。

因爲subpage不只僅須要定位到徹底二叉樹在那個節點,還須要知道在long的第幾個 而且是第幾位,因此要複雜一些:

在這裏插入圖片描述

經過一個long的前32位來表示subpage的第幾個long的第幾位上面,經過後32來表示在徹底二叉樹的那個節點上面,完美。

分配核心

分配入口:ByteBuf byteBuf = alloc.directBuffer(256);

進行跟進代碼:

在這裏插入圖片描述

咱們來看:PooledByteBuf buf = newByteBuf(maxCapacity);

構建PooledByteBuf對象。最後返回PooledByteBuf對象。

咱們來看下類繼承結構:

在這裏插入圖片描述

全部ByteBuf byteBuf = alloc.directBuffer(256);這句話是沒有什麼問題的,不會報錯。

咱們來看看newByteBuf(maxCapacity)的細節實現:

在這裏插入圖片描述

這裏藉助了Netty增長實現的Recycler對象池技術。Recycler設計也很是精巧,後續能夠專門寫篇Recycler文章,今天不是重點,咱們只要知道因爲分配PolledByteBuf對象的代價有點大,若是須要頻繁使用到PolledByteBuf對象,而且對性能有所要求,那麼池化技術是一個不錯的選擇(好比咱們之前使用的線程池、數據庫鏈接池等都是相似道理),**池化技術在必定程度上面減小了頻繁建立對象帶來的性能開銷。**其實這個相似的思想很是常見(好比咱們查詢數據庫成本高,緩存到redis,思路也是同樣的),在本篇後續中還能夠體會到(PoolThreadCache)。

經過PooledByteBuf buf = newByteBuf(maxCapacity);僅僅是獲取到了一個初始對象而已。

分配的核心在:allocate(cache, buf, reqCapacity);

在這裏插入圖片描述

  • 先嚐試在
    進行分配,根據不一樣的類型定位到不一樣的Caches,若是有進行分配直接返回。
  • 若是
    分配不了,進行
    上面分配。

步驟分配細節:看看須要分配的是什麼類型 page仍是subpage,若是是subpage在根據看看是tinySubpagePools仍是smallSubpagePools,找到對應的槽位,看看鏈表裏是否有可用的PoolSubpage,若是有就進行分配修改標記退出,若是沒有就現須要在先分配一個page了,根據chunklist的這些看看是否有合適的,若是有合適的,那麼在這些已經有的chunk上面進行分配一個page (分配page也是這個狀況了)

以後在根據分配到的page,進行該請求大小的分配 (因爲一個page能夠存儲不少同大小的數量)須要用long的位標記,表示該位置分配了,而且修改徹底二叉樹的父等值,分配結束。若是沒有chunk那麼須要新分配一塊chunk以後重複上面步驟便可。

釋放核心

釋放入口 : byteBuf.release();

進行跟進代碼:

在這裏插入圖片描述

在這裏插入圖片描述

在這裏插入圖片描述

經過這段代碼咱們就這段放入到相應的queue了:

在這裏插入圖片描述

緩存到了對應的Cache的queue裏面了。

文章github源代碼地址:nettydemo,或者公號回覆「Netty」獲取源碼地址。


若是讀完以爲有收穫的話,歡迎點贊、關注、加公衆號【匠心零度】,查閱更多精彩歷史!!!

相關文章
相關標籤/搜索