为了提升消息接收和发送性能,Netty针对 ByteBuf 的申请和释放采用池化技术,通过PooledByteBufAllocator可以创建基于内存池分配的ByteBuf对象,这样就避免了每次消息读写都申请和释放ByteBuf。由于ByteBuf涉及byte[]数组的创建和销毁,对于性能要求苛刻的系统而言,重用ByteBuf带来的性能收益是非常可观的。
内存池是一把双刃剑,如果使用不当,很容易带来内存泄漏和内存非法引用等问题,另外,除了内存池,Netty同时也支持非池化的ByteBuf,多种类型的ByteBuf功能存在些差异,使用不当很容易带来各种问题。
问题代码如图:
这里很容易以为是响应消息ByteBuf未释放引起的,其实不是,因为ctx.writeAndFlush(respMsg)方法中netty会分两种情况自动释放:
1.如果是堆内存(PooledHeapByteBuf),则将HeapByteBuffer转换成DirectByteBuffer,并释放PooledHeapByteBuf到内存池,如果消息完整地被写到SocketChannel中,则释放DirectByteBuffer。
2.如果是DirectByteBuffer,则不需要转换,在消息发送完成后,由ChannelOutboundBuffer的remove()负责释放。
而这里即使继承自ChannelInbundHandlerAdapter,请求消息reqMsg未被释放,这是因为实现的是channelRead,而不是channelRead0。
【ByteBuf内存释放误区】
分为4中情况:
1.
基于内
存池的请求ByteBuf
这类 ByteBuf主要包括PooledDirectByteBuf和PooledHeapByteBuf,它由NettyNioEventLoop线程在处理Channel的读操作时分配,需要在业务ChannelInboundHandler处理完请求消息之后释放(通常在解码之后),它的释放有两种策略。
策略1
业务ChannelIlnboundHandler继承自SimpleChannelInboundHandler,实现它的抽象方法channelReadO(ChannelHandlerContext ctx, I msg),ByteBuf的释放业务不用关心,由SimpleChannelInboundHandler负责释放。
策略2
在业务ChannelInboundHandler中调用ctx.fireChannelRead(msg)方法,让请求
消息继续向后执行,直到调用DefaultChannelPipeline的内部类TailContext,由它来负责释放请求消息,代码如下( TailContext):
2.基于非内存池的请求ByteBuf
如果业务使用非内存池模式覆盖Netty默认的内存池模式创建请求ByteBuf,例如通过如下代码修改内存申请策略为Unpooled:也需要按照内存池的方式释放内存。
3.基于内存池的响应ByteBuf
根据之前的分析,
只要调用了writeAndFlush或者flush方法,在消息发送完成后都会由Netty框架进行内存释放,业务不需要主动释放内存。
4.基于非内存池的响应ByteBuf
无论是基于内存池还是非内存池分配的 ByteBuf,如果是堆内存,则将堆内存转换成堆外内存,然后释放 HeapByteBuffer,待消息发送完成,再释放转换后的 DirectByteBuf;
如果是 DirectByteBuffer,则不需要转换,待消息发送完成之后释放。因此对于需要发送的响应ByteBuf,由业务创建,但是不需要由业务来释放。