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