内存申请机制梳理

作为高性能的NIO通信框架,Netty对于协议消息接入的内存申请及动态扩容的处理策略,值得我们借鉴和学习。下面分析Netty消息接入的内存申请方式,以及当ByteBuf容量不足时的动态扩容机制。对于API网关类产品,尤其有借鉴意义。
当SocketChannel有消息读取时,需要预先分配一个ByteBuffer 接收消息,但是由于 消息尚未被读取,无法知道需要申请一个多大的ByteBuffer。如果小了就需要重新申请一 个大的 ByteBuffer,将原来已经读取到较小ByteBuffer的byte数组拷贝到新申请的ByteBuffer,同时释放老的 ByteBuffer,这会增加一次内存拷贝操作,而且申请了两个ByteBuffer,更占用内存。如果过大,则会导致内存资源的浪费。下面分析一下Netty 的解决策略
Netty NIO 消息读取入口是NioByteUnsafe的read方法:
默认通过AdaptiveRecvByteBufAllocator来进行内存分配,它的成员变量定义如下:
它分别定义了3个系统默认值:最小缓冲区长度为64字节、初始容量为1024字节、最大容量为65536字节。还定义了两个动态调整容量的步进参数:扩张的步进索引为4、收缩的步进索引为1
定义了长度的向量表SIZE_TABLE并初始化
向量数组的每个值都对应一个Buffer容量,当容量小于512字节的时候,由于缓冲区已经比较小,需要降低步进值,容量每次下调的幅度要小些;当容量大于512字节时,说明需要解码的消息码流比较大,这时采用调大步进幅度的方式减小动态扩张的频率,所以它采用512字节的倍数进行扩张。接下来重点分析AdaptiveRecvByteBufAllocator的相关方法。
getSizeTableIndex(final int size)代码如下:
该方法的作用是根据容量Size查找容量向量表对应的索引——这是典型的二分查找法.
包含一个静态内部类HandleImpl,如下:
它有5个成员变量,分别是对应向量表的最小索引、最大索引、当前索引、下一次预分配的Buffer大小,以及是否立即执行容量收缩操作。
接下来重点分析它的record(int actualReadBytes)方法:当NioSocketChannel执行完读操作,会计算本次轮询读取的总字节数,它就是参数actualKeadbyles,1 recolu/f2,根据实际读取的字节数对 ByteBuf进行动态伸缩,代码如下:
首先,对当前索引做步进缩减,然后获取收缩后索引对应的容量,与实际读取的字节数进行比对,如果发现实际读取的字节数小于收缩后的容量,则重新对当前索引进行赋值,取收缩后的索引和最小索引中的较大者作为新的索引。然后,为下一次缓冲区容量分配赋值——新的索引对应容量向量表中的容量。相反,如果实际读取的字节数大于之前预分配的初始容量,则说明实际分配的容量不足,需要动态扩张。重新计算索引,选取“当前索引+扩张步进”和最大索引中的较小作为当前索引值,然后对下次缓冲区的容量值进行重新分配,完成缓冲区容量的动态扩张。通过上述分析得知,AdaptiveRecvByteBufAllocator根据本次读取的实际字节数对下次接收缓冲区的容量进行动态调整。

【ByteBuffer动态扩容源码】

以UnpooledHeapByteBuf为例进行说明,它的capacity(int newCapacity)方法如下( UnpooledHeapByteBuf类):
方法入口首先对新容量进行合法性校验,如果大于容量上限或者小于0,则抛出IllegalArgumentException异常。判断新的容量值是否大于当前的缓冲区容量,如果大于则需要进行动态扩展,通过byte[]newArray = new byte[newCapacity]创建新的缓冲区字节数组,然后通过System.arraycopy进行内存复制,将旧的字节数组复制到新创建的字节数组,最后调用setArray替换旧的字节数组,代码如下:

【Netty的动态缓冲区分配器优点】

( 1) Netty作为一个通用的NIO框架,不能对用户的应用场景进行假设,可以使用它 做流式计算,也可以用它做RCP框架,不同的应用场景,传输的码流大小千差万别,无 论初始化时分配的是 32KB还是1MB,都会随着应用场景的变化而变得不合适。因此, Netty根据上次实际读取的码流大小对下次的接收Buffer缓冲区进行预测和调整,能够最 大限度地满足不同行业的应用场景的需要
(2)综合性能更高。 分配容量过大会导致内存占用开销增加,后续的 下降;容量过小需要频繁地内存扩张来接收大的请求消息,同样会导致性能下降
(3)更节约内存。 假如通常情况请求消息大小平均值为1MB左右,接收缓冲区大小为1.2MB, 突然某个客户发送了一个10MB的附件,接收缓冲区扩张为10MB以读取该附件,如果缓 冲区不能收缩,每次缓冲区创建都会分配10MB的内存,但是后续所有的消息都是1MB 左右的,这样会导致内存的浪费,如果并发客户端过多,可能会导致内存溢出并宕机
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

0x13

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值