Netty中常见的IllegalReferenceCountException异常原因及解决
Contents
问题代码
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();
}
}
上面的代码原目的是回显的, 但请求一下, 就会报:
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). 源码如下:
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
boolean release = true;
try {
if (acceptInboundMessage(msg)) {
@SuppressWarnings("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.例如:@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(); } }