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

  

netty的异常分析 IllegalReferenceCountException refCnt: 0的更多相关文章

  1. Netty源码分析第4章(pipeline)---->第6节: 传播异常事件

    Netty源码分析第四章: pipeline 第6节: 传播异常事件 讲完了inbound事件和outbound事件的传输流程, 这一小节剖析异常事件的传输流程 首先我们看一个最最简单的异常处理的场景 ...

  2. [编织消息框架][netty源码分析]11 UnpooledHeapByteBuf 与 ByteBufAllocator

    每种ByteBuf都有相应的分配器ByteBufAllocator,类似工厂模式.我们先学习UnpooledHeapByteBuf与其对应的分配器UnpooledByteBufAllocator 如何 ...

  3. [编织消息框架][netty源码分析]11 ByteBuf 实现类UnpooledHeapByteBuf职责与实现

    每种ByteBuf都有相应的分配器ByteBufAllocator,类似工厂模式.我们先学习UnpooledHeapByteBuf与其对应的分配器UnpooledByteBufAllocator 如何 ...

  4. Netty源码分析第5章(ByteBuf)---->第9节: ByteBuf回收

    Netty源码分析第五章: ByteBuf 第九节: ByteBuf回收 之前的章节我们提到过, 堆外内存是不受jvm垃圾回收机制控制的, 所以我们分配一块堆外内存进行ByteBuf操作时, 使用完毕 ...

  5. netty源码解解析(4.0)-18 ChannelHandler: codec--编解码框架

    编解码框架和一些常用的实现位于io.netty.handler.codec包中. 编解码框架包含两部分:Byte流和特定类型数据之间的编解码,也叫序列化和反序列化.不类型数据之间的转换. 下图是编解码 ...

  6. Netty源码分析之ByteBuf引用计数

    引用计数是一种常用的内存管理机制,是指将资源的被引用次数保存起来,当被引用次数变为零时就将其释放的过程.Netty在4.x版本开始使用引用计数机制进行部分对象的管理,其实现思路并不是特别复杂,它主要涉 ...

  7. netty源码解解析(4.0)-10 ChannelPipleline的默认实现--事件传递及处理

    事件触发.传递.处理是DefaultChannelPipleline实现的另一个核心能力.在前面在章节中粗略地讲过了事件的处理流程,本章将会详细地分析其中的所有关键细节.这些关键点包括: 事件触发接口 ...

  8. Netty源码分析第2章(NioEventLoop)---->第8节: 执行任务队列

      Netty源码分析第二章: NioEventLoop   第八节: 执行任务队列 继续回到NioEventLoop的run()方法: protected void run() { for (;;) ...

  9. Netty源码分析第4章(pipeline)---->第7节: 前章节内容回顾

    Netty源码分析第四章: pipeline 第七节: 前章节内容回顾 我们在第一章和第三章中, 遗留了很多有关事件传输的相关逻辑, 这里带大家一一回顾 首先看两个问题: 1.在客户端接入的时候, N ...

随机推荐

  1. php 正则表达式一.函数解析

    php正则表达式官方手册参考....... 一.php中 常用的正则表达式函数 1.preg_match与preg_match_all preg_match: 函数信息 preg_match_all: ...

  2. reg_action

    function check_email($win) { $win = trim($win); $reg = '/\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)* ...

  3. IO流入门-概述

    纲要 Java流概述 文件流 缓冲流 转换流 打印流 对象流 File类 流的概念 按方向划分:输入流和输出流,是相对内存而言的.从内存出来是输出,到内存中就是输入.输入流又叫做InputStream ...

  4. HTTP请求 蜘蛛的 user-agent

    百度爬虫 * Baiduspider+(+http://www.baidu.com/search/spider.htm”) google爬虫 * Mozilla/5.0 (compatible; Go ...

  5. Yii2.0的乐观锁与悲观锁(转)

    原文:Yii2.0的乐观锁与悲观锁 Web应用往往面临多用户环境,这种情况下的并发写入控制, 几乎成为每个开发人员都必须掌握的一项技能. 在并发环境下,有可能会出现脏读(Dirty Read).不可重 ...

  6. 关于var关键字的详解

    var 在很多语言中都比较常见,到底var是什么,如何应用,下面就笔者常用的javascript.c#对var进行说明: var 是 variable(变量,可变物)的简写.在多种计算机编程语言中,v ...

  7. Q35+uefi or bios+legacy // PCI | PCIE

    1:首先统一可扩展固件接口(UEFI)是一种规范定义操作系统和平台固件之间的软件接口. UEFI旨在替代基本输入/输出系统(BIOS)固件接口.(legacy) 硬件平台厂商越来越多地采用UEFI管理 ...

  8. 微信小程序组件button

    表单组件button:官方文档 Demo Code: var types=['default', 'primary', 'warn']; var pageObject = { data: { defa ...

  9. dymaic方式的Json序列化

    from:http://stackoverflow.com/questions/3142495/deserialize-json-into-c-sharp-dynamic-object If you ...

  10. Canvas:技术小结

    Canvas:技术小结 资料 [教程:MDN官方中文教程] https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API/Tutorial [ ...