https://github.com/ZhuBaker/ant-rpc/blob/master/ant-remoting/src/main/java/com/antrpc/remoting/model/Heartbeats.java
package com.antrpc.remoting.model;
import com.antrpc.common.protocal.AntProtocal;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
/**
* Created with IntelliJ IDEA.
* Description: 自定义心跳协议
* User: zhubo
* Date: 2018-03-30
* Time: 9:19
*/
public class Heartbeats {
private static final ByteBuf HEARTBEAT_BUF;
static {
ByteBuf buf = Unpooled.buffer(AntProtocal.HEAD_LENGTH);
buf.writeShort(AntProtocal.MAGIC);//2
buf.writeByte(AntProtocal.HEARTBEAT);//1
buf.writeByte(0);//1
buf.writeLong(0);//8
buf.writeInt(0);//4
buf.writeByte(0);//1 compress
HEARTBEAT_BUF = buf.asReadOnly();
}
/**
* 返回公用的心跳内容.
* @return
*/
public static ByteBuf heartbeatContent(){
//复制当前对象,复制后的对象与前对象共享缓冲区,且维护自己的独立索引
return HEARTBEAT_BUF.duplicate();
}
}
设计代码的初始目的是为了按照协议栈的格式 封装统一的心跳信息,来达到内存复用的目的。首先将ByteBuf中填充心跳数据,然后利用 装饰者模式 封装其为ReadOnlyByteBuf 对象并返回,对每次调用heartbeatContent 方法时,都返回一个共享内存空间,但是独立维护自己读写index索引的DuplicatedByteBuf对象。
我们开始设计初期的目的是没问题的,如果仅仅如此,这样设计就可以达到我们想要的效果。
但是当我们调用第二次 heartbeatContent() 获取 原对象封装的DuplicatedByteBuf对象候 对其调用
ctx.writeAndFlush(Heartbeats.heartbeatContent());
会出现如下异常
io.netty.util.IllegalReferenceCountException: refCnt: 0
at io.netty.buffer.AbstractByteBuf.ensureAccessible(AbstractByteBuf.java:1423) ~[netty-all-4.1.0.Final.jar:4.1.0.Final]
at io.netty.buffer.UnpooledHeapByteBuf.capacity(UnpooledHeapByteBuf.java:102) ~[netty-all-4.1.0.Final.jar:4.1.0.Final]
at io.netty.buffer.ReadOnlyByteBuf.capacity(ReadOnlyByteBuf.java:408) ~[netty-all-4.1.0.Final.jar:4.1.0.Final]
at io.netty.buffer.AbstractByteBuf.setIndex(AbstractByteBuf.java:126) ~[netty-all-4.1.0.Final.jar:4.1.0.Final]
at io.netty.buffer.ReadOnlyByteBuf.<init>(ReadOnlyByteBuf.java:50) ~[netty-all-4.1.0.Final.jar:4.1.0.Final]
at io.netty.buffer.ReadOnlyByteBuf.duplicate(ReadOnlyByteBuf.java:278) ~[netty-all-4.1.0.Final.jar:4.1.0.Final]
下面为堆栈中自己写的程序代码抛出异常的地方
at com.antrpc.remoting.model.Heartbeats.heartbeatContent(Heartbeats.java:33) ~[classes/:na]
at com.antrpc.remoting.netty.idle.ConnectorIdleStateTrigger.userEventTriggered(ConnectorIdleStateTrigger.java:30) ~[classes/:na]
这是因为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对象的引用计数会减一。导致第二次使用同样对象的包装对象时,出现引用计数的问题。
package com.antrpc.remoting.model;
import com.antrpc.common.protocal.AntProtocal;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
/**
* Created with IntelliJ IDEA.
* Description: 自定义心跳协议
* User: zhubo
* Date: 2018-03-30
* Time: 9:19
*/
public class Heartbeats {
private static final ByteBuf HEARTBEAT_BUF;
static {
ByteBuf buf = Unpooled.buffer(AntProtocal.HEAD_LENGTH);
buf.writeShort(AntProtocal.MAGIC);//2 magic
buf.writeByte(AntProtocal.HEARTBEAT);//1 code
buf.writeByte(0);//1 type
buf.writeLong(0);//8 id
buf.writeInt(0);//4 length
buf.writeByte(0);//1 compress
HEARTBEAT_BUF = buf.asReadOnly();
}
/**
* 返回公用的心跳内容.
* @return
*/
public static ByteBuf heartbeatContent(){
// 我们可以调用ByteBuf.retain()将引用计数加1
HEARTBEAT_BUF.retain();
//复制当前对象,复制后的对象与前对象共享缓冲区,且维护自己的独立索引
return HEARTBEAT_BUF.duplicate();
}
}
参考文章:
https://blog.csdn.net/linuu/article/details/51141655
https://blog.csdn.net/jiangguilong2000/article/details/78602103
http://damacheng009.iteye.com/blog/2013657
http://ju.outofmemory.cn/entry/85894
https://blog.csdn.net/u010853261/article/details/53690780
https://www.cnblogs.com/zemliu/p/3667332.html