Netty源码分析之ByteBuf引用计数
引用计数是一种常用的内存管理机制,是指将资源的被引用次数保存起来,当被引用次数变为零时就将其释放的过程。Netty在4.x版本开始使用引用计数机制进行部分对象的管理,其实现思路并不是特别复杂,它主要涉及跟踪某个对象被引用的次数。在Netty具体代码中需要通过引用计数进行内存管理的对象,会基于ReferenceCounted接口实现,其中引用计数大于0时则代表该对象被引用不会释放,当引用计数减少到0时,该对象就会被释放。通过引用计数机制,Netty可以很好的实现内存管理,引用计数减少到0时要么直接释放内存,要么放回内存池中重复利用。
1、基本示例
下面先通过一个简单示例看下Netty中引用计数机制的使用
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) { ByteBuf recvBuffer = (ByteBuf) msg;// 申请ByteBuf 需要主动释放
if(recvBuffer.isDirect()){
System.err.println(true);
}
PooledByteBufAllocator allocator = new PooledByteBufAllocator(true);
ByteBuf sendBuffer = allocator.buffer();//申请池化直接内存
System.err.println("sendBuffer的引用计数:"+sendBuffer.refCnt());
sendBuffer.retain();
System.err.println("sendBuffer的引用计数:"+sendBuffer.refCnt());
sendBuffer.release();
System.err.println("sendBuffer的引用计数:"+sendBuffer.refCnt());
try {
byte[] bytesReady = new byte[recvBuffer.readableBytes()];
recvBuffer.readBytes(bytesReady);
System.out.println("channelRead收到数据:"+ BytesUtils.toHexString(bytesReady));
byte[] sendBytes = new byte[] {0x7E,0x01,0x02,0x7e};
sendBuffer.writeBytes(sendBytes);
ctx.writeAndFlush(sendBuffer);
System.err.println("sendBuffer的引用计数:"+sendBuffer.refCnt());
}catch (Exception e) {
// TODO: handle exception
System.err.println(e.getMessage());
}finally {
System.err.println("recvBuffer的引用计数:"+recvBuffer.refCnt());
recvBuffer.release(); //此处需要释放
System.err.println("recvBuffer的引用计数:"+recvBuffer.refCnt());
} }
输出结果如下,通过示例可以看出retain方法会增加计数引用,release方法会减少计数引用
true
sendBuffer的引用计数:1
sendBuffer的引用计数:2
sendBuffer的引用计数:1
sendBuffer的引用计数:0
recvBuffer的引用计数:1
recvBuffer的引用计数:0
AbstractReferenceCountedByteBuf实现了对ByteBuf的内存管理,以实现内存的回收、释放或者重复利用 ,AbstractReferenceCountedByteBuf的继承实现关系如下图所示

2、ReferenceCounted接口定义
首先是ReferenceCounted接口的定义
public interface ReferenceCounted {
/**
* Returns the reference count of this object. If {@code 0}, it means this object has been deallocated.
* 返回对象的引用计数
*/
int refCnt();
/**
* Increases the reference count by {@code 1}.
* 增加引用计数
*/
ReferenceCounted retain();
/**
* Increases the reference count by the specified {@code increment}.
* 引用计数增加指定值
*/
ReferenceCounted retain(int increment);
/**
* Records the current access location of this object for debugging purposes.
* If this object is determined to be leaked, the information recorded by this operation will be provided to you
* via {@link ResourceLeakDetector}. This method is a shortcut to {@link #touch(Object) touch(null)}.
* 记录该对象的当前访问位置,用于调试。
* 如果确定该对象被泄露,将提供此操作记录的信息给您
*/
ReferenceCounted touch();
/**
* Records the current access location of this object with an additional arbitrary information for debugging
* purposes. If this object is determined to be leaked, the information recorded by this operation will be
* provided to you via {@link ResourceLeakDetector}.
* 记录该对象的当前访问位置,附加信息用于调试。
* 如果确定该对象被泄露,将提供此操作记录的信息给您
*/
ReferenceCounted touch(Object hint);
/**
* Decreases the reference count by {@code 1} and deallocates this object if the reference count reaches at
* {@code 0}.
*
* @return {@code true} if and only if the reference count became {@code 0} and this object has been deallocated
* 引用计数减少,如果计数变为了0,则释放对象资源
* 如果对象资源被释放,则返回true,否则返回false
*/
boolean release();
/**
* Decreases the reference count by the specified {@code decrement} and deallocates this object if the reference
* count reaches at {@code 0}.
*
* @return {@code true} if and only if the reference count became {@code 0} and this object has been deallocated
* 引用计数-指定值,如果计数变为了0,则释放对象资源或交回到对象池
* 如果对象资源被释放,则返回true,否则返回false
*/
boolean release(int decrement);
}
3、AbstractReferenceCountedByteBuf源码分析
AbstractReferenceCountedByteBuf对ReferenceCounted进行了具体实现,retain与release两个方法通过CAS方式对引用计数refcnt进行操作,下面对其源码进行简单分析
初始化
引用计数初始值refCnt 使用关键字volatile修饰,保证线程的可见性,同时使用偶数,引用增加通过位移操作实现,提高运算效率。
采用 AtomicIntegerFieldUpdater 对象,通过CAS方式更新refCnt,以实现线程安全,避免加锁,提高效率。
private static final long REFCNT_FIELD_OFFSET;
//采用 AtomicIntegerFieldUpdater 对象,CAS方式更新refCnt
private static final AtomicIntegerFieldUpdater<AbstractReferenceCountedByteBuf> refCntUpdater =
AtomicIntegerFieldUpdater.newUpdater(AbstractReferenceCountedByteBuf.class, "refCnt"); //refCnt 实际值为偶数,采用位移操作提高效率
// even => "real" refcount is (refCnt >>> 1); odd => "real" refcount is 0
@SuppressWarnings("unused")
private volatile int refCnt = 2;
retain操作
上面示例中每调用一次retain方法,引用计数就会累加一次,我们看下源码中retain的具体实现
public ByteBuf retain() {
return retain0(1);
}
@Override
public ByteBuf retain(int increment) {
return retain0(checkPositive(increment, "increment"));
}
//计数器增值操作
private ByteBuf retain0(final int increment) {
// all changes to the raw count are 2x the "real" change
int adjustedIncrement = increment << 1; // overflow OK here 真正的计数都是2倍递增
int oldRef = refCntUpdater.getAndAdd(this, adjustedIncrement); //通过CAS方式递增并获取原值
if ((oldRef & 1) != 0) {//判断奇偶,正常情况这里应该都是偶数
throw new IllegalReferenceCountException(0, increment);
}
// don't pass 0! 如果计数小于等于0,以及整型范围越界(0x7fffffff+1)抛出异常
if ((oldRef <= 0 && oldRef + adjustedIncrement >= 0)
|| (oldRef >= 0 && oldRef + adjustedIncrement < oldRef)) {
// overflow case
refCntUpdater.getAndAdd(this, -adjustedIncrement);
throw new IllegalReferenceCountException(realRefCnt(oldRef), increment);
}
return this;
}
release操作
通过调用release方法,对引用计数做减值操作,源码中release的具体实现要注意的是由于引用计数以2倍递增,所以引用次数= 引用计数/2,当decrement=refcnt/2 也就是引用次数=释放次数时,代表ByteBuf不再被引用,执行内存释放或放回内存池的操作。
//计数器减值操作
private boolean release0(int decrement) {
int rawCnt = nonVolatileRawCnt(), realCnt = toLiveRealCnt(rawCnt, decrement); //对计数器进行除以2操作,也就是引用次数
/**
* /这里如注意 你传入的减值参数decrement = realCnt 时 等同于 引用次数=释放次数,直接进行释放操作
*/
if (decrement == realCnt) {
if (refCntUpdater.compareAndSet(this, rawCnt, 1)) { //CAS方式置为1
deallocate();//内存释放或放回内存池
return true;
}
return retryRelease0(decrement);//进入具体操作
}
return releaseNonFinal0(decrement, rawCnt, realCnt);
} private boolean releaseNonFinal0(int decrement, int rawCnt, int realCnt) {
//如果decrement 小于 realCnt,通过CAS方式减去decrement*2
if (decrement < realCnt
// all changes to the raw count are 2x the "real" change
&& refCntUpdater.compareAndSet(this, rawCnt, rawCnt - (decrement << 1))) {
return false;
}
return retryRelease0(decrement);
} private boolean retryRelease0(int decrement) {
for (;;) {
int rawCnt = refCntUpdater.get(this), realCnt = toLiveRealCnt(rawCnt, decrement);
if (decrement == realCnt) {
if (refCntUpdater.compareAndSet(this, rawCnt, 1)) {
deallocate();
return true;
}
} else if (decrement < realCnt) {//如果decrement 小于 realCnt,通过CAS方式减去decrement*2
// all changes to the raw count are 2x the "real" change
if (refCntUpdater.compareAndSet(this, rawCnt, rawCnt - (decrement << 1))) {
return false;
}
} else {
throw new IllegalReferenceCountException(realCnt, -decrement);
}
Thread.yield(); // this benefits throughput under high contention
}
} /**
* Like {@link #realRefCnt(int)} but throws if refCnt == 0
*/
private static int toLiveRealCnt(int rawCnt, int decrement) {
if ((rawCnt & 1) == 0) {
return rawCnt >>> 1;
}
// odd rawCnt => already deallocated
throw new IllegalReferenceCountException(0, -decrement);
}
4、总结
以上我们围绕AbstractReferenceCountedByteBuf对Netty引用计数的具体实现进行了分析,可以看到Netty在实现引用计数的同时,结合CAS、位移计算等方式,保证了运算效率和线程安全,在实际项目中我们遇到类似应用场景也都可以借鉴参考,如数据发送次数,商品剩余数量等计数场景的实现。希望本文对大家能有所帮助,其中如有不足与不正确的地方还望指正与海涵,十分感谢。
关注微信公众号,查看更多技术文章。

Netty源码分析之ByteBuf引用计数的更多相关文章
- [编织消息框架][netty源码分析]11 ByteBuf 实现类UnpooledHeapByteBuf职责与实现
每种ByteBuf都有相应的分配器ByteBufAllocator,类似工厂模式.我们先学习UnpooledHeapByteBuf与其对应的分配器UnpooledByteBufAllocator 如何 ...
- [编织消息框架][netty源码分析]10 ByteBuf 与 ByteBuffer
因为jdk ByteBuffer使用起来很麻烦,所以netty研发出ByteBuf对象维护管理内存使用ByteBuf有几个概念需要知道1.向ByteBuf提取数据时readerIndex记录最后读取坐 ...
- Netty源码分析之ByteBuf(二)—内存分配器ByteBufAllocator
Netty中的内存分配是基于ByteBufAllocator这个接口实现的,通过对它的具体实现,可以用来分配我们之前描述过的任意类型的BytebBuf实例:我们先看一下ByteBufAllocator ...
- [编织消息框架][netty源码分析]13 ByteBuf 实现类CompositeByteBuf职责与实现
public class CompositeByteBuf extends AbstractReferenceCountedByteBuf implements Iterable<ByteBuf ...
- Netty源码分析之ByteBuf(一)—ByteBuf中API及类型概述
ByteBuf是Netty中主要的数据容器与操作工具,也是Netty内存管理优化的具体实现,本章我们先从整体上对ByteBuf进行一个概述: AbstractByteBuf是整个ByteBuf的框架类 ...
- [编织消息框架][netty源码分析]12 ByteBuf 实现类UnpooledDirectByteBuf职责与实现
public class UnpooledDirectByteBuf extends AbstractReferenceCountedByteBuf { private final ByteBufAl ...
- Netty源码分析第5章(ByteBuf)---->第5节: directArena分配缓冲区概述
Netty源码分析第五章: ByteBuf 第五节: directArena分配缓冲区概述 上一小节简单分析了PooledByteBufAllocator中, 线程局部缓存和arean的相关逻辑, 这 ...
- Netty源码分析第5章(ByteBuf)---->第6节: 命中缓存的分配
Netty源码分析第6章: ByteBuf 第六节: 命中缓存的分配 上一小节简单分析了directArena内存分配大概流程, 知道其先命中缓存, 如果命中不到, 则区分配一款连续内存, 这一小节带 ...
- Netty源码分析第5章(ByteBuf)---->第9节: ByteBuf回收
Netty源码分析第五章: ByteBuf 第九节: ByteBuf回收 之前的章节我们提到过, 堆外内存是不受jvm垃圾回收机制控制的, 所以我们分配一块堆外内存进行ByteBuf操作时, 使用完毕 ...
随机推荐
- 巩固javaweb第十二天
巩固内容: HTML 图像- 图像标签( <img>)和源属性(Src) 在 HTML 中,图像由<img> 标签定义. <img> 是空标签,意思是说,它只包含属 ...
- A Child's History of England.5
Above all, it was in the Roman time, and by means of Roman ships, that the Christian Religion was fi ...
- 修改linux文件权限命令:chmod 转载至 Avril 的随笔
Linux系统中的每个文件和目录都有访问许可权限,用它来确定谁可以通过何种方式对文件和目录进行访问和操作. 文件或目录的访问权限分为只读,只写和可执行三种.以文件为例,只读权限表示只允许读其内容,而禁 ...
- Docker学习(一)——安装docker
Suse12上安装docker 对于suse13.2之后的版本,因为docker已经被添加到了suse仓库中,直接使用sudo zypper install docker即可. suse12不 ...
- 转 Android中Activity的启动模式(LaunchMode)和使用场景
转载请注明出处:http://blog.csdn.net/sinat_14849739/article/details/78072401本文出自Shawpoo的专栏我的简书:简书 一.为什么需要启动模 ...
- Mybatis-运行原理
一.mybatis分层图 二.运行流程 根据全局配置文件创建sqlSessionFactory对象 根据全局配置文件的io流来构建SqlSessionFactoryBuilder对象: 解析(XmlC ...
- redis入门到精通系列(四):Jedis--使用java操作redis详解
(一)前言 如果不把数据库和后端语言联系起来,就起不到数据库应该要起到的作用.Java语言通过JDBC操作mysql,用Jedis操作redis.当然了,java操作redis的方式不止jedis一种 ...
- 【Python】【Basic】【数据类型】基本数据类型
1.数字 int(整型) 在32位机器上,整数的位数为32位,取值范围为-2**31-2**31-1,即-2147483648-2147483647 在64位系统上,整数的位数为64位,取值范围为-2 ...
- java标识接口
标识接口是没有任何方法和属性的接口,标识接口不对实现类有任何语义上的要求,仅仅表明它的实现类属于一个特定的类型.它非常类似于Web 2.0中的TAG的概念,Java使用它标识某一类对象.主要有两个用途 ...
- LuoguB2075 幂的末尾 题解
Content 求 \(a^b\) 的末三位. 数据范围:\(1\leqslant a\leqslant 100\),\(1\leqslant b\leqslant 10^4\). Solution ...