1. 程式人生 > >看完這篇還不清楚Netty的記憶體管理,那我就哭了!

看完這篇還不清楚Netty的記憶體管理,那我就哭了!

說明

在學習Netty的時候,ByteBuf隨處可見,但是如何高效分配ByteBuf還是很複雜的,Netty的池化記憶體分配這塊還是比較難的,很多人學習過,看過但是還是雲裡霧裡的,本篇文章就是主要來講解:Netty分配池化的堆外記憶體的細節,期待可以讓你明白!!!

由於為了更好的表達,文章中的圖我最少畫了6小時,畫的不熟悉,並且也強調一些細節上。

由於該原始碼中涉及到大量的二進位制操作,建議看看我之前寫的2篇二進位制文章:java二進位制相關基礎,二進位制實戰技巧。

ByteBuf重要性

ByteBuf在Netty中一直存在,讀寫必備!ByteBuf是Netty的資料容器,高效分配ByteBuf至關重要!

Netty從socket讀取資料。

Netty準備把資料寫到socket中去。

通過這裡我們就可以看到,再把資料寫socket的之前會判斷是否是堆外記憶體,如果不是會構造一個directbuffer物件的,細節程式碼如下:

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

            return newDirectBuffer(buf);
        }

所以本篇文章就是主要來講解:Netty分配池化的堆外記憶體的細節,其實分配堆記憶體的細節很多也是類似的。

備註: 為什麼不是堆外記憶體還要轉堆外記憶體,為什麼加這個判斷,我之前也不理解,忽然有天和滌生大佬討論,討論討論就清晰了,後續有空寫篇。

總覽

本次主要討論的是關於池化記憶體的分配,PooledByteBufAllocator就是netty分配池化記憶體的操作入口。

其提供對外常用操作api:

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”獲取原始碼地址。


如果讀完覺得有收穫的話,歡迎點贊、關注、加公眾號 [匠心零度] ,查閱更多精彩歷史!!!

相關推薦

清楚Netty記憶體管理

說明 在學習Netty的時候,ByteBuf隨處可見,但是如何高效分配ByteBuf還是很複雜的,Netty的池化記憶體分配這塊還是比較難的,很多人學習過,看過但是還是雲裡霧裡的,本篇文章就是主要來講解:Netty分配池化的堆外記憶體的細節,期待可以讓你明白!!! 由於為了更好的表達,文章中的圖我最少畫了6小

會自定義 View 跪搓衣板

自定義 View 在實際使用的過程中,我們經常會接到這樣一些需求,比如環形計步器,柱狀圖表,圓形頭像等等,這時我們通常的思路是去Google 一下,看看 github 上是否有我們需要的這些控制元件,但是如果網上收不到這樣的控制元件呢?這時我們經常需要自定義 View 來滿足需求。 接下來讓我們開啟自定

會 GestureDetector 手勢檢測跪搓衣板

引言 在 android 開發過程中,我們經常需要對一些手勢,如:單擊、雙擊、長按、滑動、縮放等,進行監測。這時也就引出了手勢監測的概念,所謂的手勢監測,說白了就是對於 GestureDetector 的用法的使用和注意要點的學習。注:由於縮放手勢獨有的複雜性,我打算後期將其單獨拿出來歸納總結。 像網上其

會用Git

你使用過 Git 嗎?也許你已經使用了一段時間,但它的許多奧祕仍然令人困惑。 Git 是一個版本控制系統,是任何軟體開發專案中的主要內容。通常有兩個主要用途:程式碼備份和程式碼版本控制。你可以逐步處理程式碼,在需要回滾到備份副本的過程中儲存每一步的進度! 常見的問題是 Git 很難使用。有時版本和分支不同步,

會Elasticsearch跪搓衣板90%程式設計師已收藏

生活中的資料 搜尋引擎是對資料的檢索,所以我們先從生活中的資料說起。我們生活中的資料總體分為兩種: 結構化資料 非

最詳細的python 操作 mongodb教程會隨時找

條件 cnblogs 江蘇 。。 location flag pre del 修改字段 準備 我的本機環境是: Python3.6 mongodb3.4.3 IDE: PyCharm Professional 因為要使用Python來操作數

再不會 View 的動畫框架跪搓衣板

引言 眾所周知,一款沒有動畫的 app,就像沒有靈魂的肉體,給使用者的體驗性很差。現在的 android 在動畫效果方面早已空前的發展,1.View 動畫框架 2.屬性動畫框架 3.Drawable 動畫。相比後後兩者,View 動畫框架在 Android 的最開始就已經出現,即有著非常容易學習的有點,卻也有

final、finally 和 finalize 和麵試官扯皮沒問題

> 我把自己以往的文章彙總成為了 Github ,歡迎各位大佬 star > https://github.com/crisxuan/bestJavaer > 已提交此篇文章 `final` 是 Java 中的關鍵字,它也是 Java 中很重要的一個關鍵字,final 修飾的類、方法、變數有不同的含義

文章懂Python裝飾器?

1、必備   2.需求來了 初創公司有N個業務部門,1個基礎平臺部門,基礎平臺負責提供底層的功能,如:資料庫操作、redis呼叫、監控API等功能。業務部門使用基礎功能時,只需呼叫基礎平臺提供的功能即可。如下: 目前公司有條不紊的進行著,但是,以前基礎平臺的開發

文章知道解軟體測試你找

測試行業小故事 我曾今不止一次見過:對軟體測試行業不甚瞭解的新人與已經在測試行業摸爬滾打幾年的前輩之間的互相指責和爭執。 新人因為不瞭解測試行業,也不瞭解提問的藝術,所以問的都是自己比較關注的問題和一些基礎問題。前輩因為對測試行業有一定的瞭解,掌握一定的知識技術,早就

分析懂分散式事務請給差評

0 題記 又或者在網上購物明明已經扣款,但是卻告訴我沒有發生交易。這一系列情況都是因為沒有事務導致的。這說明了事務在生活中的一些重要性。 有了事務,你去小賣鋪買東西,那就是一手交錢一手交貨。有了事務,你去網上購物,扣款即產生訂單交易。 1 事務的具體定義 事務

別說你懂Hinton大神的膠囊網路capsule network

倒計時 2 天 來源 | 王的機器(公眾號ID:MeanMachine1031) 作者 | 王聖元 0 引言 斯蒂文認為機器學習有時候像嬰兒學習,特別是在物體識別上。比如嬰兒首先學會識別邊界和顏色,然後將這些資訊用於識別形狀和圖形等更復雜的實體。比如在人臉識別上

媽媽會擔心你Docker入門?

“上週物件突然心血來潮說想養個小寵物,我問想養啥她又說隨便,你看著辦!!! 這我真的比較難辦啊!但是咱們程式設計師能有個物

知道這些佇列這些圖白作

佇列(queue)是一種採用先進先出(FIFO)策略的抽象資料結構,即最先進佇列的資料元素,同樣要最先出佇列。佇列跟我們排隊買票一樣,先來排隊的肯定先買票,後來排隊的的後買到票。佇列如下圖所示: 佇列有兩個重要的概念,一個叫隊頭,一個叫隊尾,隊頭指向的是第一個元素,而隊尾指向的是最後一個元素。佇列跟棧一樣

大寫的服懂RocketMQ算

## 目錄 1. RocketMQ介紹 2. RocketMQ概念 3. 為什麼要用RocketMQ? 1. 非同步解耦 2. 削峰填谷 3. 分散式事務最終一致性 4. 資料分發 4. RocketMQ架構 5. RocketMQ訊息型別 1. 普通訊息 2.

文章你清楚的知道 ZooKeeper的 概念

問題 sta leader 監聽服務 生成 項目 一輪 ges 服務註冊 前言 相信大家對 ZooKeeper 應該不算陌生。但是你真的了解 ZooKeeper 是個什麽東西嗎?如果別人/面試官讓你給他講講 ZooKeeper 是個什麽東西,你能回答到什麽地步呢? 我本人曾

面試在被紅-黑樹虐?動圖文章輕鬆反虐面試官

  網上有很多紅-黑樹的段子,很多人都說,紅-黑樹只會存在於段子裡,不會在面試中或者實際專案中讓你實現。來看看網友都是怎麼說的: 通常,如果有面試官問我紅黑數這種問題。 我一般扭頭就走。 不是因為,這個職位用不到還問這個。 而是因為。 我 tmd 真的不會啊 - -|

「讀懂原始碼系列1」在恐懼讀原始碼?不怕

一個小需求 事情的起因,是昨天有一個新的需求被提出。 需求是要實現,讓我們自己定製的彈出層,具備按下 ESC 也能退出的功能。我把任務交給了同組的小夥伴S去實現。(這個專案用到了vue技術棧,以及餓了麼的UI框架。) 我開完會回來,發現他還在處理那個功能,但好像遇到了什麼瓶頸。於是,我就問他,卡在了什麼

【Android開發經驗】APP的快取檔案到底應該存在哪?文章你應該自己清楚

只要是需要進行聯網獲取資料的APP,那麼不管是版本更新,還是圖片快取,都會在本地產生快取檔案。那麼,這些快取檔案到底放在什地方合適呢?系統有沒有給我們提供建議的快取位置呢?不同的快取位置有什麼不同呢?今天這篇文章就是主要來說明這個問題的。     首先,我們要知道,在A

在靠“喂喂喂”來測語音通話質量嗎文章你能掌握正確姿勢。

語音通話開發,對於一般開發者來說比較神祕,很多朋友不太清楚如何全面的評估第三方的音訊引擎,如何科學的選擇一家靠譜的語音通話服務供應商。 很多朋友還停留在這樣的初級階段:把demo調通,找幾個人喂喂喂……憑自己優異的聽覺感受一下。整個測試過程就完成了,廠商也就這麼“愉快”