1. 程式人生 > >深入淺出Netty記憶體管理:PoolSubpage

深入淺出Netty記憶體管理:PoolSubpage

本系列:

上一節中分析瞭如何在poolChunk中分配一塊大於pageSize的記憶體,但在實際應用中,存在很多分配小記憶體的情況,如果也佔用一個page,明顯很浪費。針對這種情況,Netty提供了PoolSubpage把poolChunk的一個page節點8k記憶體劃分成更小的記憶體段,通過對每個記憶體段的標記與清理標記進行記憶體的分配與釋放。

01
PoolSubpage

12345678910111213141516171819202122232425 finalclassPoolSubpage<T>{// 當前page在chunk中的idprivatefinalintmemoryMapIdx;// 當前page在chunk.memory的偏移量privatefinalintrunOffset;// page大小privatefinalintpageSize;//通過對每一個二進位制位的標記來修改一段記憶體的佔用狀態privatefinallong[]bitmap;PoolSubpage
<T>prev;PoolSubpage<T>next;booleandoNotDestroy;// 該page切分後每一段的大小intelemSize;// 該page包含的段數量privateintmaxNumElems;privateintbitmapLength;// 下一個可用的位置privateintnextAvail;// 可用的段數量privateintnumAvail;...}

假設目前需要申請大小為4096的記憶體:

1234567 longallocate(intnormCapacity){if((normCapacity&subpageOverflowMask)!=0){// >= pageSizereturnallocateRun(normCapacity);}else{returnallocateSubpage(normCapacity);}}

因為 4096 < pageSize(8192),所以採用 allocateSubpage 進行記憶體分配,具體實現如下:

123456789101112131415161718192021222324252627 privatelongallocateSubpage(intnormCapacity){// 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){intd=maxOrder;// subpages are only be allocated from pages i.e., leavesintid=allocateNode(d);if(id<0){returnid;}finalPoolSubpage<T>[]subpages=this.subpages;finalintpageSize=this.pageSize;freeBytes-=pageSize;intsubpageIdx=subpageIdx(id);PoolSubpage<T>subpage=subpages[subpageIdx];if(subpage==null){subpage=newPoolSubpage<T>(head,this,id,runOffset(id),pageSize,normCapacity);subpages[subpageIdx]=subpage;}else{subpage.init(head,normCapacity);}returnsubpage.allocate();}}

1、Arena負責管理PoolChunk和PoolSubpage;
2、allocateNode負責在二叉樹中找到匹配的節點,和poolChunk不同的是,只匹配葉子節點;
3、poolChunk中維護了一個大小為2048的poolSubpage陣列,分別對應二叉樹中2048個葉子節點,假設本次分配到節點2048,則取出poolSubpage陣列第一個元素subpage;
4、如果subpage為空,則進行初始化,並加入到poolSubpage陣列;

subpage初始化實現如下:

123456789101112 PoolSubpage(PoolSubpage<T>head,PoolChunk<T>chunk,intmemoryMapIdx,intrunOffset,intpageSize,elemSize){this.chunk=chunk;this.memoryMapIdx=memoryMapIdx;this.runOffset=runOffset;this.pageSize=pageSize;bitmap=newlong[pageSize>>>10];// pageSize / 16 / 64init(head,elemSize);}

1、預設初始化bitmap長度為8,這裡解釋一下為什麼只需要8個元素:其中分配記憶體大小都是處理過的,最小為16,說明一個page可以分成8192/16 = 512個記憶體段,一個long有64位,可以描述64個記憶體段,這樣只需要512/64 = 8個long就可以描述全部記憶體段了。
2、init根據當前需要分配的記憶體大小,確定需要多少個bitmap元素,實現如下:

1234567891011121314151617 voidinit(PoolSubpage<T>head,intelemSize){doNotDestroy=true;this.elemSize=elemSize;if(elemSize!=0){maxNumElems=numAvail=pageSize/elemSize;nextAvail=0;bitmapLength=maxNumElems>>>6;if((maxNumElems&63)!=0){bitmapLength++;}for(inti=0;i<bitmapLength;i++){bitmap[i]=0;}}addToPool(head);}

下面通過分佈申請4096和32大小的記憶體,說明如何確定bitmapLength的值:

  1. 比如,當前申請大小4096的記憶體,maxNumElems 和 numAvail 為2,說明一個page被拆分成2個記憶體段,2 >>> 6 = 0,且2 & 63 != 0,所以bitmapLength為1,說明只需要一個long就可以描述2個記憶體段狀態。
  2. 如果當前申請大小32的記憶體,maxNumElems 和 numAvail 為 256,說明一個page被拆分成256個記憶體段, 256>>> 6 = 4,說明需要4個long描述256個記憶體段狀態。

下面看看subpage是如何進行記憶體分配的:

123456789101112131415161718192021 longallocate(){if(elemSize==0){returntoHandle(0);}if(numAvail==0||!doNotDestroy){return-1;}finalintbitmapIdx=getNextAvail();intq=bitmapIdx>>>6;intr=bitmapIdx&63;assert(bitmap[q]>>>r&1)==0;bitmap[q]|=1L<<r;if(--numAvail==0){removeFromPool();}returntoHandle(bitmapIdx);}

1、方法getNextAvail負責找到當前page中可分配記憶體段的bitmapIdx;
2、q = bitmapIdx >>> 6,確定bitmap陣列下標為q的long數,用來描述 bitmapIdx 記憶體段的狀態;
3、bitmapIdx & 63將超出64的那一部分二進位制數抹掉,得到一個小於64的數r;
4、bitmap[q] |= 1L << r將對應位置q設定為1;

如果以上描述不直觀的話,下面換一種方式解釋,假設需要分配大小為128的記憶體,這時page會拆分成64個記憶體段,需要1個long型別的數字描述這64個記憶體段的狀態,所以bitmap陣列只會用到第一個元素。

02
狀態轉換

getNextAvail如何實現找到下一個可分配的記憶體段?

12345678 privateintgetNextAvail(){intnextAvail=this.nextAvail;if(nextAvail>=0){this.nextAvail=-1;returnnextAvail;}returnfindNextAvail();}

1、如果nextAvail大於等於0,說明nextAvail指向了下一個可分配的記憶體段,直接返回nextAvail值;
2、每次分配完成,nextAvail被置為-1,這時只能通過方法findNextAvail重新計算出下一個可分配的記憶體段位置。

1234567 privateintfindNextAvail(){finallong[]bitmap=this.bitmap;finalintbitmapLength=this.bitmapLength;for(inti=0;i>>=1;}return-1;}

1、~bits != 0說明這個long所描述的64個記憶體段還有未分配的;
2、(bits & 1) == 0 用來判斷該位置是否未分配,否則bits又移一位,從左到右遍歷值為0的位置;

至此,subpage記憶體段已經分配完成。

打賞支援我寫出更多好文章,謝謝!

打賞作者

打賞支援我寫出更多好文章,謝謝!

相關推薦

深入淺出Netty記憶體管理PoolSubpage

本系列: 上一節中分析瞭如何在poolChunk中分配一塊大於pageSize的記憶體,但在實際應用中,存在很多分配小記憶體的情況,如果也佔用一個page,明顯很浪費。針對這種情況,Netty提供了PoolSubpage把poolChunk的一個page節點8k

深入淺出Netty記憶體管理PoolChunk

多年之前,從C記憶體的手動管理上升到java的自動GC,是歷史的巨大進步。然而多年之後,netty的記憶體實現又曲線的回到了手動管理模式,正印證了馬克思哲學觀:社會總是在螺旋式前進的,沒有永遠的最好。的確,就記憶體管理而言,GC給程式設計師帶來的價值是不言而喻的,

深入淺出Netty記憶體管理PoolArena

前面分別分析了PoolChunk、PoolSubpage和PoolChunkList,本文主要分析PoolArena。 PoolArena 應用層的記憶體分配主要通過如下實現,但最終還是委託給PoolArena實現。 PooledByteBufAllocat

深入淺出Netty記憶體管理PoolChunkList

前面兩篇分別分析了PoolChunk和PoolSubpage的實現,本文主要分析管理PoolChunk生命週期的PoolChunkList。 PoolChunkList PoolChunkList負責管理多個chunk的生命週期,在此基礎上對記憶體分配進行進一步的優化。

資料庫記憶體管理tcmalloc與jemalloc

  [[email protected] ~]# tar zxvf libunwind-1.2.1.tar.gz  [[email protected] ~]# cd libunwind-1.2

Sqlserver記憶體管理限制最大佔用記憶體

一、Sqlserver對系統記憶體的管理原則是:按需分配,且貪婪(用完不還)。它不會自動釋放記憶體,因此執行結果集大的sql語句時,資料取出後,會一直佔用記憶體,直到佔滿機器記憶體(並不會撐滿,還是有個最大限制,比機器記憶體稍小),在重啟服務前,sqlserver不會釋放該記

Spark靜態記憶體管理StaticMemoryManager

例如Executor的可用Heap大小是10G,實際上Spark只能使用90%,也就是9G的大小,是由spark.storage.safetyFraction來控制。 Spark1.6.X以前JVM到底可以快取多少資料? (1)單個Executor的Cache資料量計算公式: Heap Size * spar

iOS效能優化之記憶體管理Analyze、Leaks、Allocations的使用和案例程式碼

一. 一些相關概念 1.記憶體空間的劃分: 我們知道,一個程序佔用的記憶體空間,包含5種不同的資料區:(1)BSS段:通常是存放未初始化的全域性變數;(2)資料段:通常是存放已初始化的全域性變數。(3)程式碼段:通常是存放程式執行程式碼。(4)堆:通常是用於存放程序執行中被

Linux記憶體管理HighMemory

HighMemory介紹 Linux一般把整個4GB可以map的記憶體中的1GB用於低端記憶體。從0xC0000000開始的話(CONFIG_PAGE_OFFSET配置),低端記憶體的地址範圍就是0xC0000000到high_memory地址。 high_

iOS記憶體管理記憶體洩露除錯的常用技巧

在往下看之前請下載例項MemoryProblems,我們將以這個工程展開如何檢查和解決記憶體問題。 懸掛指標問題 懸掛指標(Dangling Pointer)就是當指標指向的物件已經釋放或回收後,但沒有對指標做任何修改(一般來說,將它指向空指標),而是仍然指向原來已經回收的地址。如

JVM記憶體管理記憶體區域和記憶體洩漏

VM執行時資料區域 JVM執行Java程式的過程中,會使用到各種資料區域,這些區域有各自的用途、建立和銷燬時間。根據《Java虛擬機器規範(第二版)》(下文稱VM Spec)的規定,JVM包括下列幾個執行時資料區域: 1.程式計數器(Program Counter Register): 每一個Java執

Linux記憶體管理CMA

某些驅動需要用到一大塊連續的實體記憶體,但使用kmalloc等很分配很大的連續記憶體。 所以這裡有一種三星實現叫CMA的方式,來連續的大記憶體分配。 Why is it needed? Issue 1: Camera, Video Codec等Multimedia Device需要連續的數MB大小的Me

C語言動態記憶體管理malloc、realloc、calloc以及free函式

我們已經掌握的記憶體開闢方式有: int val = 20;//在棧空間上開闢四個位元組 char arr[10] = {0};//在棧空間上開闢10個位元組的連續空間 但是這種開闢空間的方式有兩個特點: 1. 空間開闢的大小是固定的。

作業系統的學習(2)——實體記憶體管理連續記憶體分配

記憶體的最小訪問單位是位元組(8it),一般計算機系統是32位匯流排,一次讀寫可以讀或者寫32位也就是4位元組。 CPU裡會看到快取記憶體,快取記憶體就是在進行讀寫指令或者指令執行的過程中,訪問資料都需要從記憶體中讀資料,如果這時候有大量資料需要讀寫或者重複利

Netty記憶體池值PoolSubpage詳解

開發十年,就只剩下這套架構體系了! >>>   

7.netty記憶體管理-ByteBuf

ByteBuf ByteBuf是什麼 ByteBuf重要API read、write、set、skipBytes mark和reset duplicate、slice、copy retain、release ByteBuf擴容 ByteBuf種類 ByteBufAllocate UnPooledB

談談Netty記憶體管理

## 前言 正是Netty的易用性和高效能成就了Netty,讓其能夠如此流行。 而作為一款通訊框架,首當其衝的便是對IO效能的高要求。 不少讀者都知道Netty底層通過使用Direct Memory,減少了核心態與使用者態之間的記憶體拷貝,加快了IO速率。但是頻繁的向系統申請Direct Memory

第四節FreeRTOS 記憶體管理

目錄 記憶體管理的介紹 記憶體碎片 Heap_1-5記憶體分配的區別 Heap_1:適用於一旦建立好記憶體,就不刪除的任務。       (本質是分配的大陣列做記憶體堆.) Heap_2:適用於重複分配和刪除具有相同堆疊空間任務。(本質是分配的大

QEMU深入淺出: guest實體記憶體管理

原  文:http://blog.vmsplice.net/2016/01/qemu-internals-how-guest-physical-ram.html 作  者:Stefan Hajnoczi 領  域:Open source and virtuali

現代作業系統第三章 記憶體管理

作業系統的工作是將這個儲存體系抽象成為一個有用的模型並將管理這個抽象模型 作業系統中管理分層儲存體系的部分稱為儲存管理器。它的任務是有效的管理記憶體,即記錄哪些記憶體是正在使用的,哪些記憶體是空閒的,在程序需要時為其分配記憶體,在程序使用完成的時候為其釋放記憶體。 3.1 無儲存器的