Netty中常见的IllegalReferenceCountException异常原因及解决
转载自:https://emacsist.github.io/2018/04/28/netty%E4%B8%AD%E5%B8%B8%E8%A7%81%E7%9A%84illegalreferencecountexception%E5%BC%82%E5%B8%B8%E5%8E%9F%E5%9B%A0%E5%8F%8A%E8%A7%A3%E5%86%B3/
Netty中常见的IllegalReferenceCountException异常原因及解决
问题代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | package hello.in; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.HttpContent; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; public class EchoHandler extends SimpleChannelInboundHandler<HttpContent> { @Override protected void channelRead0(final ChannelHandlerContext ctx, final HttpContent msg) { System.out.println("收到" + msg); ByteBuf echoMsg = msg.content(); System.out.println(new String(ByteBufUtil.getBytes(echoMsg))); DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, echoMsg); response.headers().set("Content-Type", "text/plain"); ctx.write(response).addListener(ChannelFutureListener.CLOSE); } @Override public void channelReadComplete(final ChannelHandlerContext ctx) { ctx.flush(); } @Override public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) { cause.printStackTrace(); ctx.close(); } } |
上面的代码原目的是回显的, 但请求一下, 就会报:
1 2 3 4 5 6 | io.netty.util.IllegalReferenceCountException: refCnt: 0, increment: 1 at io.netty.buffer.AbstractReferenceCountedByteBuf.release0(AbstractReferenceCountedByteBuf.java:100) at io.netty.buffer.AbstractReferenceCountedByteBuf.release(AbstractReferenceCountedByteBuf.java:84) at io.netty.handler.codec.http.DefaultHttpContent.release(DefaultHttpContent.java:94) at io.netty.util.ReferenceCountUtil.release(ReferenceCountUtil.java:88) ... |
原因
首先要重点强调的是: SimpleChannelInboundHandler
它会自动进行一次释放(即引用计数减1). 源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { boolean release = true; try { if (acceptInboundMessage(msg)) { @SuppressWaings("unchecked") I imsg = (I) msg; channelRead0(ctx, imsg); } else { release = false; ctx.fireChannelRead(msg); } } finally { if (autoRelease && release) { ReferenceCountUtil.release(msg); } } } |
我们继承这个类, 一般是要重写 channelRead0()
这个方法的, 但实际上 Netty 内部处理的是 channelRead()
方法, 只是它通过模板模式来进行调用而已.
然后, 我们的代码里 DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, echoMsg);
这里使用了原 msg.content()
的 ByteBuf
, 然后调用 ctx.write(response)
后, 就会导致 msg
的引用计数减1了.(这时, 引用计数变成0了~)
可以调用
echoMsg.refCnt();
来获取当前引用计数值. 在ctx.write(...)
前后加一行打印, 就可以发现,ctx.write(...)
完之后, 引用计数减少了1.
然后最后 channelRead0()
执行完毕返回了, SimpleChannelInboundHandler
的模板方法还会再一次进行释放 release
, 这时就会触发 IllegalReferenceCountException
异常了.(参考 [翻译]Netty中的引用计数对象
).
解决
- 如果不想创建新的数据, 则可以直接在原对象里调用
echoMsg.retain()
进行引用计数加1.例如:
1 2 3 4 5 6 7 8 9 10 | @Override protected void channelRead0(final ChannelHandlerContext ctx, final HttpContent msg) { System.out.println("收到" + msg); ByteBuf echoMsg = msg.content(); echoMsg.retain(); System.out.println(new String(ByteBufUtil.getBytes(echoMsg))); DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, echoMsg); response.headers().set("Content-Type", "text/plain"); ctx.write(response).addListener(ChannelFutureListener.CLOSE); } |
即上面的 echoMsg.retain()
方法.
- 构造 response 对象时, 不要复用
echoMsg
对象, 例如:
1 | DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.copiedBuffer(echoMsg)); |
即, 使用 Unpooled.copiedBuffer(...)
来复制多一份内存数据~
- 直接使用
ChannelInboundHandlerAdapter
自动手动处理释放, 以免像SimpleChannelInboundHandler
那样导致多次释放引用计数对象~
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | package hello.in; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.HttpContent; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; public class EchoHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(final ChannelHandlerContext ctx, final Object msg) { if (msg instanceof HttpContent) { manual(ctx, (HttpContent) msg); } } protected void manual(final ChannelHandlerContext ctx, final HttpContent msg) { System.out.println("收到" + msg); ByteBuf echoMsg = msg.content(); System.out.println(new String(ByteBufUtil.getBytes(echoMsg))); DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, echoMsg); response.headers().set("Content-Type", "text/plain"); ctx.write(response).addListener(ChannelFutureListener.CLOSE); } @Override public void channelReadComplete(final ChannelHandlerContext ctx) { ctx.flush(); } @Override public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) { cause.printStackTrace(); ctx.close(); } } |
作者:Full Stack Developme
来源链接:https://blog.csdn.net/lizhengyu891231/article/details/88553560
版权声明:
1、JavaClub(https://www.javaclub.cn)以学习交流为目的,由作者投稿、网友推荐和小编整理收藏优秀的IT技术及相关内容,包括但不限于文字、图片、音频、视频、软件、程序等,其均来自互联网,本站不享有版权,版权归原作者所有。
2、本站提供的内容仅用于个人学习、研究或欣赏,以及其他非商业性或非盈利性用途,但同时应遵守著作权法及其他相关法律的规定,不得侵犯相关权利人及本网站的合法权利。
3、本网站内容原作者如不愿意在本网站刊登内容,请及时通知本站(javaclubcn@163.com),我们将第一时间核实后及时予以删除。