在netty 4.1中正确释放引用计数的ByteBuf对象

时间:2017-05-24 06:22:05

标签: java netty nio

因此,我们目前正在基于MQTT的消息传递后端中将netty 3.x升级到netty 4.1。在我们的应用程序中,我们使用自定义MQTT消息解码器和编码器。

对于我们的解码器,我目前正在使用ByteToMessageDecoder,如下所示:

public class MqttMessageDecoder extends ByteToMessageDecoder {

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        if (in.readableBytes() < 2) {
            return;
        }

        .....
        .....
        .....

        byte[] data = new byte[msglength];
        in.resetReaderIndex();
        in.readBytes(data);
        MessageInputStream mis = new MessageInputStream(
                new ByteArrayInputStream(data));
        Message msg = mis.readMessage();
        out.add(msg);
        ReferenceCountUtil.release(in);
    }
}

其中Message是我们的自定义对象,会传递给下一个ChannelHandler的{​​{1}}。正如您所看到的,只要我从中创建channelRead()对象,我就完成了传入的ByteBuf对象in。因此,由于Message在netty中被引用计数,因此我需要通过调用ByteBuf来释放in对象是否正确?理想情况下,根据doc,这似乎是正确的。但是,当我这样做时,我似乎面临着异常:

ReferenceCountUtil.release(in)

这告诉我,当子通道关闭时,管道中的所有处理程序都会被一个接一个地删除。当这个解码器处理程序关闭时,我们显式释放附加到此解码器的Wed May 24 io.netty.channel.DefaultChannelPipeline:? WARN netty-workers-7 An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception. io.netty.channel.ChannelPipelineException: com.bsb.hike.mqtt.MqttMessageDecoder.handlerRemoved() has thrown an exception. at io.netty.channel.DefaultChannelPipeline.callHandlerRemoved0(DefaultChannelPipeline.java:631) [netty-all-4.1.0.Final.jar:4.1.0.Final] at io.netty.channel.DefaultChannelPipeline.destroyDown(DefaultChannelPipeline.java:867) [netty-all-4.1.0.Final.jar:4.1.0.Final] at io.netty.channel.DefaultChannelPipeline.access$300(DefaultChannelPipeline.java:45) [netty-all-4.1.0.Final.jar:4.1.0.Final] at io.netty.channel.DefaultChannelPipeline$9.run(DefaultChannelPipeline.java:874) [netty-all-4.1.0.Final.jar:4.1.0.Final] at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:339) [netty-all-4.1.0.Final.jar:4.1.0.Final] at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:374) [netty-all-4.1.0.Final.jar:4.1.0.Final] at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:742) [netty-all-4.1.0.Final.jar:4.1.0.Final] at java.lang.Thread.run(Thread.java:745) [na:1.8.0_72-internal] Caused by: io.netty.util.IllegalReferenceCountException: refCnt: 0, decrement: 1 at io.netty.buffer.AbstractReferenceCountedByteBuf.release(AbstractReferenceCountedByteBuf.java:111) ~[netty-all-4.1.0.Final.jar:4.1.0.Final] at io.netty.handler.codec.ByteToMessageDecoder.handlerRemoved(ByteToMessageDecoder.java:217) ~[netty-all-4.1.0.Final.jar:4.1.0.Final] at io.netty.channel.DefaultChannelPipeline.callHandlerRemoved0(DefaultChannelPipeline.java:626) [netty-all-4.1.0.Final.jar:4.1.0.Final] ... 7 common frames omitted ,当调用下面的方法时,会导致ByteBuf异常。

这是IllegalReferenceCountException

AbstractReferenceCountedByteBuf#release

然后释放@Override public boolean release() { for (;;) { int refCnt = this.refCnt; if (refCnt == 0) { throw new IllegalReferenceCountException(0, -1); } if (refCntUpdater.compareAndSet(this, refCnt, refCnt - 1)) { if (refCnt == 1) { deallocate(); return true; } return false; } } } 对象的正确方法是什么,以免遇到此问题?

我正在使用ByteBuf -

PooledByteBufAllocator

如果您需要有关配置的更多信息,请与我们联系。

修改

作为Ferrybig答案的附加组件,new ServerBootstrap().childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) 自行处理传入的ByteToMessageDecoder#channelRead的释放。请参阅ByteBuf块 -

finally

如果将入站@Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof ByteBuf) { CodecOutputList out = CodecOutputList.newInstance(); try { ByteBuf data = (ByteBuf) msg; first = cumulation == null; if (first) { cumulation = data; } else { cumulation = cumulator.cumulate(ctx.alloc(), cumulation, data); } callDecode(ctx, cumulation, out); } catch (DecoderException e) { throw e; } catch (Throwable t) { throw new DecoderException(t); } finally { if (cumulation != null && !cumulation.isReadable()) { numReads = 0; cumulation.release(); cumulation = null; } else if (++ numReads >= discardAfterReads) { // We did enough reads already try to discard some bytes so we not risk to see a OOME. // See https://github.com/netty/netty/issues/4275 numReads = 0; discardSomeReadBytes(); } int size = out.size(); decodeWasNull = !out.insertSinceRecycled(); fireChannelRead(ctx, out, size); out.recycle(); } } else { ctx.fireChannelRead(msg); } } 传输到管道下的下一个通道处理程序,则此ByteBuf的引用计数会增加ByteBuf,因此如果解码器之后的下一个处理程序是您的业务处理程序(通常是这种情况),您需要在那里释放ByteBuf#retain对象以避免任何内存泄漏。这里也提到了docs

2 个答案:

答案 0 :(得分:2)

并非所有处理程序都需要销毁传入的bytebuf。 ng serve就是其中之一。

这样做的原因是这个处理程序收集多个传入的bytebuf,并将它们作为1个连续的字节流公开给你的应用程序,以便于编码,而不需要自己处理这些块

请记住,您仍然需要使用ByteToMessageDecoderreadBytes手动释放您创建的任何bytebuf,如javadoc所述。

答案 1 :(得分:1)

@Ferrybig他的答案已经足够好了。

这里我想添加一些处理ByteBuf发布的简单约定。

如果您有这样的InboundHandler:

public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = ByteBuf(msg)
...
// no fireChannelRead()
}

因为您在它之后拦截了InboundHandler(如果有),并且没有人正在处理ByteBuf对象,您需要手动释放它。

你也可以调用fireChannelRead()。 Netty默认添加尾部处理程序以释放ByteBuf。所以在调用fireChannelRead()之后你不需要释放它。

如果我的回答有任何问题,请告诉我。