6 内存分配:ByteBuf
最底层
- 作用
将数据从底层IO里面读到ByteBuf,传递给应用程序,应用程序处理完之后,再把数据封装成ByteBuf传回给IO
主要内容:
-
内存与内存管理器的抽象
-
不同规格大小和不同类别的内存的分配策略
-
内存的回收过程
6.1 ByteBuf结构及重要API
结构:三个指针:readerIndex、writerIndex、capacity
写数据写不下去时,writable bytes可以扩容,直到capacity等于maxCapacity()
- API
readXXX:不同基本类型的读(从ByteBuf读取),调用时,readerIndex会向右移动,即向writerIndex方向移动
writeXXX:不同基本类型的写(写到ByteBuf),调用时,writerIndex会向右移动,即向capacity方向移动
set:直接在指定索引上设置值,跟三个指针没有关系
mark、reset:如markReaderIndex方法,在读之前把readerIndex保存起来,这样读取完毕之后可以调用resetReaderIndex方法进行复原,writer同理
readableBytes:writerIndex - readerIndex
writableBytes:capacity - writerIndex
6.2 ByteBuf分类
AbstractByteBuf:基本骨架实现,具体读写方法交给子类
指针定义的位置和一些指针的位置计算方法;
读写字节最终调用的方法_setByte 和 _getByte为抽象方法,因为不同ByteBuf有不同实现,所以交给子类
readerIndex和writerIndex以byte为单位,每读写一个byte就加1
- ByteBuf分类:三个角度,全部八种类型
1.Pooled和Unpooled:池化和非池化
每次进行内存分配时,Pooled的bytebuf是从预先分配好的内存中取一段封装成ByteBuf,交给应用程序;Unpooled则直接调用操作系统API申请内存
2.Unsafe和非Unsafe:
Unsafe的bytebuf可以直接拿到其在JVM里的具体内存调用unsafe的方法进行读写,即依赖JDK的Unsafe对象;非Unsafe则不会,即没有依赖
3.Heap和Direct:
Heap在堆上分配内存;
Direct直接调用JDKAPI进行内存分配,不受JVM控制,不会参与到GC的过程,需要手动释放,否则会造成内存泄漏。
Heap底层依赖一个byte类型的数组,内存相关的操作都是基于这个数组上;
Direct底层依赖nio的DirectByteBuffer对象。
6.3 ByteBufAllocator分析
Netty的内存管理器,6.2中所有的ByteBuf都是通过它分配出来的,这是一个接口
重要方法:buffer方法(依赖于具体实现)、ioBuffer方法(钟意direct)、heapBuffer方法、directBuffer方法;
这里区分了heap和direct
6.3.1 AbstractByteBufAllocator
实现了ByteBufAllocator大部分方法,暴露了newHeapBuffer方法和newDirectBuffer方法实现扩展,如:
其buffer方法分配内存时,只能判断是heap还是direct,pooled或Unpooled要交给子类实现,可以交给这两个方法,即上图的两个池化和非池化
6.3.2 ByteBufAllocator两大子类
一个池化PooledByteBufAllocator一个非池化 UnPooledByteBufAllocator
这里会直接判断是否加上unsafe:根据平台是否支持来判断(newHeapBuffer和newDirectBuffer);
这时候三个角度的实现就都清楚了:这里实现池化非池化和unsafe非unsafe,AbstractByteBufAllocator实现head或direct
6.3.2.1 UnPooledByteBufAllocator
- heap内存的分配(newHeapBuffer)
如果支持Unsafe,则返回UnpooledUnsafeHeapByteBuf,操作基于Unsafe类;(heap使用byte数组)
如果不支持,则返回UnpooledHeapByteBuf,操作基于数组+下标/ByteBuffer的API。
- direct内存的分配(newDirectBuffer)
如果支持Unsafe,则返回UnpooledUnsafeDirectByteBuf,操作基于Unsafe类;(direct使用DirectByteBuffer)
如果不支持,则返回UnpooledDirectByteBuf
得到ByteBuffer后,还要保存内存地址(基地址,使用unsafe类获取)
如何获取某个地址?基地址+偏移地址,然后使用unsafe类获取
哪个快?Unsafe快,因为可以直接操作地址
6.3.2.2 PooledByteBufAllocator
相比UnPooledByteBufAllocator,实现了池化(提前分配内存)、ByteBuf对象的复用和缓存(将用完的内存存起来)
- 分配内存流程
1.拿到线程局部缓存PoolThreadCache
newDirect/HeapBuffer方法可能会被多线程调用,所以要保证线程安全:其类型为PoolThreadLocalCache,扩展自FastThreadLocal,封装了ThreadLocal,更快
2.在线程局部缓存的Arena上进行内存分配
PoolThreadCache维持两块内存:堆(heapArena)和堆外内存(directArena);
heapArena是PoolArena<byte[]>,directArena是PoolArena< ByteBuffer >
两者何时创建?创建内存分配器PooledByteBufAllocator的时候
两种arena数组大小?两倍CPU核数。
原因:因为NioEventLoop线程也是两倍CPU核数,说明每个线程可以独享一个arena
对于每一个arena,通过它进行分配内存时需要加锁吗?不需要
原因:上述的PoolThreadLocalCache实现了线程安全
- 结构
1.通常会创建和线程数量相等的arena, 并以数组的形式存储在PooledByteBufAllocator的成员变量中;
每一个PoolThreadCache创建的时候, 都会在当前线程拿到一个arena, 并保存在自身的成员变量中:
2.PoolThreadCache除了维护了