Skip to content

Latest commit

 

History

History
121 lines (82 loc) · 4.62 KB

21.彻底明白ByteBuf的自动释放.md

File metadata and controls

121 lines (82 loc) · 4.62 KB

21 彻底明白ByteBuf的自动释放

ByteBuf 是如何进行自动创建和自动是释放的呢,这个过程是Netty封装起来了,下面我门一起探索一些吧!

有两种场景下会自动释放

tailContext 流水线尾部自动释放

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);
       }
 }
…
}

如何让ByteBuf数据包通过流水线一路向后传递,到达末尾的TailContext呢?

如果自定义的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,利用它的自动释放功能来完成。

方式二: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),最终导致内存耗尽。