《Netty深入剖析》之六:ByteBuf及内存管理

本文详细探讨了Netty中的ByteBuf结构和API,包括ByteBuf的分类,如Pooled和Unpooled,Unsafe和非Unsafe,Heap和Direct。深入分析了ByteBufAllocator的内存分配策略,包括池化内存管理、直接内存分配和堆内存分配。文章还介绍了内存规格、缓存分配逻辑、内存分配流程以及ByteBuf的回收机制,展示了Netty如何利用池化、缓存和多级内存管理提高性能。
摘要由CSDN通过智能技术生成

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除了维护了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值