ByteBuf 是如何进行自动创建和自动是释放的呢,这个过程是Netty封装起来了,下面我门一起探索一些吧!
Netty默认会在ChannelPipline的最后添加一个TailContext(尾部上下文,也是一个入站处理器)。它实现了默认的入站处理方法, 在这些方法中会帮助完成ByteBuf内存释放的工作
只要最初的ByteBuf数据包一路向后传递,进入流水线的末端,TailContext(末尾处理器)就会自动释放掉入站的ByteBuf实例。
//流水线实现类
public class DefaultChannelPipeline implements ChannelPipeline {
//内部类:尾部处理器和尾部上下文是同一个类
final class TailContext extends AbstractChannelHandlerContext
implements ChannelInboundHandler {
//入站处理方法:读取通道
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
onUnhandledInboundMessage(ctx, msg);
}
…
}
…
//入站消息没有被处理,或者说来到了流水线末尾,释放缓冲区
protected void onUnhandledInboundMessage(Object msg) {
try {
logger.debug(…);
} finally {
//释放缓冲区
ReferenceCountUtil.release(msg);
}
}
…
}
如果自定义的InboundHandler(入站处理器)继承自ChannelInboundHandlerAdapter适配器, 那么可以在入站处理方法中调用基类的入站处理方法,演示代码如下:
public class DemoHandler extends ChannelInboundHandlerAdapter {
/**
* 出站处理方法
* @param ctx 上下文
* @param msg 入站数据包
* @throws Exception 可能抛出的异常
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)…{
ByteBuf byteBuf = (ByteBuf) msg;
//省略ByteBuf的业务处理
//调用父类的入站方法,默认的动作是将msg向下一站传递,一直到末端
super.channelRead(ctx,msg);
//方式二:手动释放ByteBuf
//byteBuf.release();
}
}
如果Handler业务处理器需要截断流水线的处理流程,不将ByteBuf数据包送入流水线末端的TailContext入站处理器,并且也不愿意手动释放ByteBuf缓冲区实例,那么该怎么办呢?
继承SimpleChannelInboundHandler,利用它的自动释放功能来完成。
以入站读数据为例,Handler业务处理器可以继承自SimpleChannelInboundHandler
基类,此时必须将业务处理代码移动到重写的channelRead0(ctx, msg)方法中。
SimpleChannelInboundHandle类的入站处理方法(如channelRead等)会在调用完实际的channelRead0()方法后帮忙释放ByteBuf实例。
如果想看看SimpleChannelInboundHandler是如何释放ByteBuf的,那么可以看看Netty源代码。截取的部分代码如下:
public abstract class SimpleChannelInboundHandler<I>
extends ChannelInboundHandlerAdapter
{
//基类的入站方法
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)…{
boolean release = […]
出站缓冲区的自动释放方式是HeadContext自动释放。出站处理用到的ByteBuf缓冲区一般是要发送的消息,通常是由Handler业务处理器所申请分配的。
例如,通过write()方法写入流水线时,调用ctx.writeAndFlush(ByteBuf msg),就会让ByteBuf缓冲区进入流水线的出站处理流程。
在每一个出站Handler业务处理器中的处理完成后,数据包(或消息)会来到出站处理的最后一棒HeadContext,在完成数据输出到通道之后,ByteBuf会被释放一次,
如果计数器为零,就将被彻底释放掉
在出站处理的流水处理过程中,在最终进行写入刷新的时候,HeadContext要通过通道实现类自身实现的doWrite()方法将ByteBuf缓冲区的字节数据发送出去 (比如复制到内部的Java NIO通道),发送完成后,doWrite()方法就会减少ByteBuf缓冲区的引用计数。
总之,在Netty应用开发中,必须密切关注ByteBuf缓冲区的释放。如果释放不及时,就会造成Netty的内存泄漏(Memory Leak),最终导致内存耗尽。