Netty之ByteBuf深入分析
[TOC]
分析思路
內(nèi)存與內(nèi)存管理器的抽象
ByteBuf 結(jié)構(gòu)以及重要的API
ByteBuf 數(shù)據(jù)結(jié)構(gòu)
* {@link ByteBuf} provides two pointer variables to support sequential
* read and write operations - {@link #readerIndex() readerIndex} for a read
* operation and {@link #writerIndex() writerIndex} for a write operation
* respectively. The following diagram shows how a buffer is segmented into
* three areas by the two pointers:
*
* <pre>
* +-------------------+------------------+------------------+
* | discardable bytes | readable bytes | writable bytes |
* | | (CONTENT) | |
* +-------------------+------------------+------------------+
* | | | |
* 0 <= readerIndex <= writerIndex <= capacity
* </pre>
*
* <h4>Readable bytes (the actual content)</h4>
read ,write set 方法
mark 和 reset方法
ByteBuf 分類
Pooled 和 Unpooled
Pooled池化內(nèi)存分配每次從預(yù)先分配好的一塊內(nèi)存取一段連續(xù)內(nèi)存封裝成ByteBuf提供給應(yīng)用程序,
Unpooled非池化每次進行內(nèi)存分配的時候調(diào)用系統(tǒng)API向操作系統(tǒng)申請一塊內(nèi)存
Unsafe 和 非Unsafe
Unsafe直接獲取ByteBuf在JVM內(nèi)存地址調(diào)用JDK的Unsafe進行讀寫操作,通過ByteBuf分配內(nèi)存首地址和當(dāng)前指針基于內(nèi)存偏移地址獲取值,
非Unsafe不依賴JDK的Unsafe對象,通過內(nèi)存數(shù)組和索引獲取值
Heap和Direct
Heap在堆上進行內(nèi)存分配,分配內(nèi)存需要被GC管理,無需手動釋放內(nèi)存,依賴底層byte數(shù)組,
Direct調(diào)用JDK的API進行內(nèi)存分配,分配內(nèi)存不受JVM控制最終不會參與GC過程,需要手動釋放內(nèi)存避免造成內(nèi)存無法釋放,依賴DirectByteBuffer對象內(nèi)存
內(nèi)存分配器ByteBufAllocator分析
ByteBufAllocator功能
buffer()方法分配內(nèi)存是否為Direct/Heap內(nèi)存依賴具體實現(xiàn),
ioBuffer()方法分配內(nèi)存更希望是適合IO的Direct Buffer,directBuffer()/headBuffer()方法堆內(nèi)/堆外進行內(nèi)存分配,compositeBuffer()方法分配將兩個ByteBuf合并變成CompositeByteBuf
AbstractByteBufAllocator
buffer()方法分配Buffer依賴實現(xiàn)分配內(nèi)存,調(diào)用directBuffer()/heapBuffer()方法分配默認(rèn)Buffer容量和最大擴充容量的ByteBuf,newDirectBuffer()/newHeapBuffer()方法分配Pooled/Unpooled依賴底層實現(xiàn)
ByteBufAllocator兩個子類
PooledByteBufAllocator從預(yù)先分配好的內(nèi)存取一段內(nèi)存,
UnpooledByteBufAllocator調(diào)用系統(tǒng)API分配內(nèi)存,調(diào)用hasUnsafe()方法獲取Unsafe決定分配Unsafe/非Unsafe
UnpooledByteBufAllocator分析
heap內(nèi)存的分配
newHeapBuffer()方法通過hasUnsafe()方法判斷是否有Unsafe
傳遞initialCapacity容量Byte數(shù)組參數(shù)setArray()方法設(shè)置array以及setIndex()方法設(shè)置讀/寫指針
創(chuàng)建UnpooledUnsafeHeapByteBuf/UnpooledHeapByteBuf
,
_get***()
方法通過Unsafe方式返回數(shù)組對象偏移量[BYTE_ARRAY_BASE_OFFSET+index]
對應(yīng)的byte/數(shù)組索引方式返回array數(shù)組index位置byte
direct內(nèi)存的分配
newDirectBuffer()方法通過hasUnsafe()方法判斷是否有Unsafe
調(diào)用allocateDirect(initialCapacity)
創(chuàng)建DirectByteBuffer
使用setByteBuffer()
方法設(shè)置buffer[UnpooledUnsafeDirectByteBuf
使用directBufferAddress()
方法獲取buffer內(nèi)存地址設(shè)置memoryAddress
創(chuàng)建UnpooledUnsafeDirectByteBuf/UnpooledDirectByteBuf
,
_get***()
方法通過addr()
方法memoryAdress+index
計算內(nèi)存地址
Unsafe
獲取對應(yīng)這塊內(nèi)存的byte/ByteBuffer
獲取buffer index
位置對應(yīng)的byte
不同規(guī)格大小和不同類別的內(nèi)存的分配策略
內(nèi)存規(guī)格介紹
0 <-tiny->512B<-small->8K<-normal->16M<-huge->
|______________________| |
SubPage Page Chunk
16M作為分界點對應(yīng)的Chunk,
所有的內(nèi)存申請以Chunk為單位向操作系統(tǒng)申請,
內(nèi)存分配在Chunk里面執(zhí)行相應(yīng)操作,
16M Chunk按照Page進行切分為2048個Page,8K Page按照SubPage切分
內(nèi)存的回收過程
常見問題
Netty 的內(nèi)存的類別有哪些?
堆內(nèi)內(nèi)存/堆外內(nèi)存
堆內(nèi)[基于2048byte字節(jié)內(nèi)存數(shù)組分配]
堆外[基于JDK的DirectByteBuffer內(nèi)存分配]
Unsafe/非Unsafe
Unsafe[通過JDK的Unsafe對象基于物理內(nèi)存地址進行數(shù)據(jù)讀寫]
非Unsafe[調(diào)用JDK的API進行讀寫]
UnPooled/Pooled
UnPooled[每次分配內(nèi)存申請內(nèi)存]
Pooled[預(yù)先分配好一整塊內(nèi)存,分配的時候用一定算法從一整塊內(nèi)存取出一塊連續(xù)內(nèi)存]
如何減少多線程內(nèi)存分配之間的競爭關(guān)系?
PooledByteBufAllocator內(nèi)存分配器結(jié)構(gòu)維護Arena數(shù)組,所有的內(nèi)存分配都在Arena上進行,
通過PoolThreadCache對象將線程和Arena進行一一綁定, 默認(rèn)情況一個Nio線程管理一個Arena實現(xiàn)多線程內(nèi)存分配相互不受影響減少多線程內(nèi)存分配之間的競爭
不同大小的內(nèi)存是如何進行分配的?
Page級別的內(nèi)存分配通過完全二叉樹的標(biāo)記查找某一段連續(xù)內(nèi)存,
Page級別以下的內(nèi)存分配首先查找到Page然后把此Page按照SubPage大小進行劃分最后通過位圖的方式進行內(nèi)存分配
不同大小的內(nèi)存是如何進行分配的愧捕?2
Netty一次向系統(tǒng)申請16M的連續(xù)內(nèi)存空間酬凳,這塊內(nèi)存通過PoolChunk對象包裝服猪,進一步的把這16M內(nèi)存分成了2048個頁(pageSize=8k)正林。頁作為Netty內(nèi)存管理的最基本的單位 首装,所有的內(nèi)存分配首先必須申請一塊空閑頁脓魏。
對于小內(nèi)存(小于4096)的分配還會將Page細(xì)化成更小的單位Subpage兰吟。Subpage按大小分有兩大類,36種情況:Tiny:小于512的情況茂翔,最小空間為16混蔼,對齊大小為16,區(qū)間為[16,512)珊燎,所以共有32種情況惭嚣。Small:大于等于512的情況,總共有四種悔政,512,1024,2048,4096晚吞。