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

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

前面分別分析了PoolChunk、PoolSubpage和PoolChunkList,本文主要分析PoolArena。

PoolArena

應用層的記憶體分配主要通過如下實現,但最終還是委託給PoolArena實現。

1 PooledByteBufAllocator.DEFAULT.directBuffer(128);

由於netty通常應用於高併發系統,不可避免的有多執行緒進行同時記憶體分配,可能會極大的影響記憶體分配的效率,為了緩解執行緒競爭,可以通過建立多個poolArena細化鎖的粒度,提高併發執行的效率。

先看看poolArena的內部結構:

poolArena

所有記憶體分配的size都會經過normalizeCapacity進行處理,當size>=512時,size成倍增長512->1024->2048->4096->8192,而size

poolArena提供了兩種方式進行記憶體分配:

  1. PoolSubpage用於分配小於8k的記憶體;
    • tinySubpagePools:用於分配小於512位元組的記憶體,預設長度為32,因為記憶體分配最小為16,每次增加16,直到512,區間[16,512)一共有32個不同值;
    • smallSubpagePools:用於分配大於等於512位元組的記憶體,預設長度為4;
    • tinySubpagePools和smallSubpagePools中的元素都是預設subpage。
  2. poolChunkList用於分配大於8k的記憶體;
    • qInit:儲存記憶體利用率0-25%的chunk
    • q000:儲存記憶體利用率1-50%的chunk
    • q025:儲存記憶體利用率25-75%的chunk
    • q050:儲存記憶體利用率50-100%的chunk
    • q075:儲存記憶體利用率75-100%的chunk
    • q100:儲存記憶體利用率100%的chunk

poolChunkList
  1. qInit前置節點為自己,且minUsage=Integer.MIN_VALUE,意味著一個初分配的chunk,在最開始的記憶體分配過程中(記憶體使用率<25%),即使完全釋放也不會被回收,會始終保留在記憶體中。
  2. q000沒有前置節點,當一個chunk進入到q000列表,如果其記憶體被完全釋放,則不再保留在記憶體中,其分配的記憶體被完全回收。

接下去看看poolArena如何實現記憶體的分配,實現如下:

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758 privatevoidallocate(PoolThreadCache cache,PooledByteBuf<T>buf,finalintreqCapacity){finalintnormCapacity=normalizeCapacity(reqCapacity);if(isTinyOrSmall(normCapacity)){// capacity < pageSizeinttableIdx;PoolSubpage<T>[]table;booleantiny=isTiny(normCapacity);if(tiny){// < 512if(cache.allocateTiny(this,buf,reqCapacity,normCapacity)){// was able to allocate out of the cache so move onreturn;}tableIdx=tinyIdx(normCapacity);table=tinySubpagePools;}else{if(cache.allocateSmall(this,buf,reqCapacity,normCapacity)){// was able to allocate out of the cache so move onreturn;}tableIdx=smallIdx(normCapacity);table=smallSubpagePools;}finalPoolSubpage<T>head=table[tableIdx];/**         * Synchronize on the head. This is needed as {@link PoolChunk#allocateSubpage(int)} and         * {@link PoolChunk#free(long)} may modify the doubly linked list as well.         */synchronized(head){finalPoolSubpage<T>s=head.next;if(s!=head){asserts.doNotDestroy&&s.elemSize==normCapacity;longhandle=s.allocate();asserthandle>=0;s.chunk.initBufWithSubpage(buf,handle,reqCapacity);if(tiny){allocationsTiny.increment();}else{allocationsSmall.increment();}return;}}allocateNormal(buf,reqCapacity,normCapacity);return;}if(normCapacity<=chunkSize){if(cache.allocateNormal(this,buf,reqCapacity,normCapacity)){// was able to allocate out of the cache so move onreturn;}allocateNormal(buf,reqCapacity,normCapacity);}else{// Huge allocations are never served via the cache so just call allocateHugeallocateHuge(buf,reqCapacity);}}

1、預設先嚐試從poolThreadCache中分配記憶體,PoolThreadCache利用ThreadLocal的特性,消除了多執行緒競爭,提高記憶體分配效率;首次分配時,poolThreadCache中並沒有可用記憶體進行分配,當上一次分配的記憶體使用完並釋放時,會將其加入到poolThreadCache中,提供該執行緒下次申請時使用。
2、如果是分配小記憶體,則嘗試從tinySubpagePools或smallSubpagePools中分配記憶體,如果沒有合適subpage,則採用方法allocateNormal分配記憶體。
3、如果分配一個page以上的記憶體,直接採用方法allocateNormal分配記憶體。

allocateNormal實現如下:

123456789101112131415161718 privatesynchronized voidallocateNormal(PooledByteBuf<T>buf,intreqCapacity,intnormCapacity){++allocationsNormal;if(q050.allocate(buf,reqCapacity,normCapacity)||q025.allocate(buf,reqCapacity,normCapacity)||q000.allocate(buf,reqCapacity,normCapacity)||qInit.allocate(buf,reqCapacity,normCapacity)||q075.allocate(buf,reqCapacity,normCapacity)||q100.allocate(buf,reqCapacity,normCapacity)){return;}// Add a new chunk.PoolChunk<T>c=newChunk(pageSize,maxOrder,pageShifts,chunkSize);longhandle=c.allocate(normCapacity);asserthandle>0;c.initBuf(buf,handle,reqCapacity);qInit.add(c);}

第一次進行記憶體分配時,chunkList沒有chunk可以分配記憶體,需通過方法newChunk新建一個chunk進行記憶體分配,並新增到qInit列表中。如果分配如512位元組的小記憶體,除了建立chunk,還有建立subpage,PoolSubpage在初始化之後,會新增到smallSubpagePools中,其實並不是直接插入到陣列,而是新增到head的next節點。下次再有分配512位元組的需求時,直接從smallSubpagePools獲取對應的subpage進行分配。

smallSubpagePools

分配記憶體時,為什麼不從記憶體使用率較低的q000開始?在chunkList中,我們知道一個chunk隨著記憶體的釋放,會往當前chunklist的前一個節點移動。

q000存在的目的是什麼?

q000是用來儲存記憶體利用率在1%-50%的chunk,那麼這裡為什麼不包括0%的chunk?

直接弄清楚這些,才好理解為什麼不從q000開始分配。q000中的chunk,當記憶體利用率為0時,就從連結串列中刪除,直接釋放實體記憶體,避免越來越多的chunk導致記憶體被佔滿。

想象一個場景,當應用在實際執行過程中,碰到訪問高峰,這時需要分配的記憶體是平時的好幾倍,當然也需要建立好幾倍的chunk,如果先從q0000開始,這些在高峰期建立的chunk被回收的概率會大大降低,延緩了記憶體的回收進度,造成記憶體使用的浪費。

那麼為什麼選擇從q050開始?

1、q050儲存的是記憶體利用率50%~100%的chunk,這應該是個折中的選擇!這樣大部分情況下,chunk的利用率都會保持在一個較高水平,提高整個應用的記憶體利用率;
2、qinit的chunk利用率低,但不會被回收;
3、q075和q100由於記憶體利用率太高,導致記憶體分配的成功率大大降低,因此放到最後;

相關推薦

深入淺出Netty記憶體管理PoolArena

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

深入淺出Netty記憶體管理PoolSubpage

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

深入淺出Netty記憶體管理PoolChunk

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

深入淺出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裡會看到快取記憶體,快取記憶體就是在進行讀寫指令或者指令執行的過程中,訪問資料都需要從記憶體中讀資料,如果這時候有大量資料需要讀寫或者重複利

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 無儲存器的

深入淺出 Java 中 JVM 記憶體管理

  Java崗位面試,JVM是對程式設計師基本功考察,通常會問你對JVM瞭解嗎?   可以分幾部分回答這個問題,首先JVM記憶體劃分 | JVM垃圾回收的含義  |  有哪些GC演算法  以及年輕代和老年代各自特點等