netty的异常 IllegalReferenceCountException refCnt: 0
这是因为Netty有引用计数器的原因,自从Netty 4开始,对象的生命周期由它们的引用计数(reference counts)管理,而不是由垃圾收集器(garbage collector)管理了。ByteBuf是最值得注意的,它使用了引用计数来改进分配内存和释放内存的性能。
在我们创建ByteBuf对象后,它的引用计数是1,当你释放(release)引用计数对象时,它的引用计数减1,如果引用计数为0,这个引用计数对象会被释放(deallocate),并返回对象池。
当尝试访问引用计数为0的引用计数对象会抛出IllegalReferenceCountException异常:
/** * Should be called by every method that tries to access the buffers content to check * if the buffer was released before. */ protected final void ensureAccessible() { if (checkAccessible && refCnt() == 0) { throw new IllegalReferenceCountException(0); } }
这个问题产生的最要原因是在第一次发送完心跳请求后,ctx.write 等一系列方法调用了ByteBuf的release方法,将其引用计数减为了0 导致的:
我们主要看其方法栈中调用信息,得到一个结论,是每次调用ctx.write/writeAndFlush, pipeline.write/writeAndFlush , 等一系列方法时,被封装的ByteBuf对象的引用计数会减一。导致第二次使用同样对象的包装对象时,出现引用计数的问题。
可以调用 echoMsg.refCnt();
来获取当前引用计数值. 在 ctx.write(...)
前后加一行打印, 就可以发现, ctx.write(...)
完之后, 引用计数减少了1.
解决
- 如果不想创建新的数据, 则可以直接在原对象里调用
echoMsg.retain()
进行引用计数加1.例如:
@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
对象, 例如:
DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.copiedBuffer(echoMsg));
即, 使用 Unpooled.copiedBuffer(...)
来复制多一份内存数据~
- 直接使用
ChannelInboundHandlerAdapter
自动手动处理释放, 以免像SimpleChannelInboundHandler
那样导致多次释放引用计数对象~
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(); } }