Netty源码解析(4)-内存分配
ByteBuf直接与底层IO打交道
1、内存类别有哪些
2、如何减少多线程内存分配竞争
3、不同大小内存是如何分配的
内存与内存管理器的抽象
不同规格大小和不同内存类别的分配策略
内存回收
ByteBuf结构
readerIndex,表示要读数据从当前指针开始读,从0到readerIndex这段空间表示是无效的
writerIndex,必须大于readerIndex,表示要写数据从当前指针开始写,从readerIndex到writerIndex这段空间表示可以读的
capacity,必须,从writerIndex到capacity这段空间表示空闲可以写的
ByteBuf API
read,从readerIndex往后开始读
write 从writerIndex往后开始写
set 不移动任何指针,直接再当前指针地方进行设置
markReaderIndex 将readerIndex进行保存起来
resetReaderIndex 将readerIndex进行恢复,这样读数据就不会移动指针
markWriterIndex 同上
resetWriterIndex 同上
readableBytes() writerIndex - readerIndex
writeableBytes() capacity - writerIndex
ByteBuf分类
Pooled和UnPooled,内存分配时从已经分配的一块内存中分配一块,有预分配过程,UnPooled就是直接向操作系统申请内存
Unsafe和非UnSafe,Unsafe可以直接拿到内存地址,可以直接拿到ByteBuf在JVM内存通过内存地址和偏移量,非unsafe可以直接调用jdk api读写,通过数组和下标访问数据,。jdk自动判断Unsafe或者非Unsafe
Heap和Direct,在堆上直接分配,jc自动回收管理,依赖一个数组,Direct调用jdk api分配不会被jvm内存回收管理,依赖jdk底层的ByteBuffer。
ByteBufAllocator 内存管理器
AbstractByteBufAllocator骨架实现
UnPooledByteBufAllocator,直接分配一个容量大小的数组
newHeapBuffer 在堆上new一个数组出来传入并保存
newDirectBuffer 依赖于jdk底层ByteBuffer,保存初始地址和容量。
PooledByteBufAllocator,首先拿到线程局部缓存,PoolThreadCache先创建PoolArena<byte[]>,PoolArena<ByteBuf>这两种内存池,在线程上的Arena上进行内存分配。创建内存分配器时会创建两种类型的数组,PoolArena.HeapArena和PoolArena.directArena
newHeapBuffer
newDirectBuffer,分配的内存要自己负责回收
DirectArena分配direct内存的流程
从对象池里面拿到的PooledByteBuf进行复用
从缓存上进行分配
从内存堆里面进行内存分配
内存规格介绍
tiny 0-512
small 512B-8k subpage
normal 8K-16M page
huge 16M以上 chunk
缓存数据结构
MemoryRegionCache
queue <chunk,handler>
sizeClass tiny[32] small[4] normal[3]
size N*16B 512B、1K、2K、4k 8k 16k 32k
内存分配先根据size从数组里面取出MemoryRegionCache,然后冲queue里面弹出一个entry给ByteBuf初始化,将弹出的entry扔到对象池进行复用,entry里面有chunk表示再那块内存上分配,handler表示起始地址。
arena,chunk,page,subpage,每个线程去分配对应内存,首先通过threadlocal,拿到pooledThreadCache。Arena直接开辟一块内存,参考pooledArena里面6个chunklist
page级别的内存分配:allocateNormal()
尝试在现有的chunk上进行分配
创建一个chunk进行内存分配
初始化PooledByteBuf
subpage内存分配
定位一个subpage对象
初始化subpage
初始化PooledByteBuf
ByteBuf对象回收
连续的内存区段加入到缓存
标记连续的内存区段为未使用
ByteBuf加入对象池