因此,我们目前正在将基于 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
的 channelRead()
.如您所见,我已处理完传入的 ByteBuf
。对象 in
一旦我创建一个 Message
从它的对象。所以,因为 ByteBuf
在 netty 中被引用计数,我需要释放 in
是否正确?通过调用 ReferenceCountUtil.release(in)
在此处反对?理想情况下,根据 doc 这似乎是正确的.但是,当我这样做时,我似乎面临异常:
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
这告诉我,当子 channel 关闭时,管道中的所有处理程序都被一个接一个地删除。当此解码器处理程序关闭时,我们将显式释放 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
-
new ServerBootstrap().childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
如果您需要有关配置的更多信息,请告诉我。
编辑:
作为 Ferrybig 答案的附加组件,ByteToMessageDecoder#channelRead
处理传入的 ByteBuf
的发布它本身。查看 finally
block -
@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
正在传输到管道中的下一个 channel 处理程序,此 ByteBuf
的引用计数通过ByteBuf#retain
增加因此,如果解码器之后的下一个处理程序是您的业务处理程序(通常是这种情况),您需要释放那个 ByteBuf
那里的对象以避免任何内存泄漏。 docs中也提到了这一点在这里。
最佳答案
并非所有处理程序都需要销毁传入的字节缓冲区。 ByteToMessageDecoder
就是其中之一。
这样做的原因是此处理程序收集多个传入的字节缓冲区,并将它们作为 1 个连续的字节流公开给您的应用程序,以便于编码,并且不需要您自己处理这些 block
请记住,如 javadoc 所述,您仍然需要使用 readBytes
或 readSlice
手动释放您创建的任何字节缓冲区。
关于java - netty 4.1正确释放引用计数的ByteBuf对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44150425/