引用计数是一种常用的内存管理机制,是指将资源的被引用次数保存起来,当被引用次数变为零时就将其释放的过程。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引用计数的更多相关文章

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

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

  2. [编织消息框架][netty源码分析]10 ByteBuf 与 ByteBuffer

    因为jdk ByteBuffer使用起来很麻烦,所以netty研发出ByteBuf对象维护管理内存使用ByteBuf有几个概念需要知道1.向ByteBuf提取数据时readerIndex记录最后读取坐 ...

  3. Netty源码分析之ByteBuf(二)—内存分配器ByteBufAllocator

    Netty中的内存分配是基于ByteBufAllocator这个接口实现的,通过对它的具体实现,可以用来分配我们之前描述过的任意类型的BytebBuf实例:我们先看一下ByteBufAllocator ...

  4. [编织消息框架][netty源码分析]13 ByteBuf 实现类CompositeByteBuf职责与实现

    public class CompositeByteBuf extends AbstractReferenceCountedByteBuf implements Iterable<ByteBuf ...

  5. Netty源码分析之ByteBuf(一)—ByteBuf中API及类型概述

    ByteBuf是Netty中主要的数据容器与操作工具,也是Netty内存管理优化的具体实现,本章我们先从整体上对ByteBuf进行一个概述: AbstractByteBuf是整个ByteBuf的框架类 ...

  6. [编织消息框架][netty源码分析]12 ByteBuf 实现类UnpooledDirectByteBuf职责与实现

    public class UnpooledDirectByteBuf extends AbstractReferenceCountedByteBuf { private final ByteBufAl ...

  7. Netty源码分析第5章(ByteBuf)---->第5节: directArena分配缓冲区概述

    Netty源码分析第五章: ByteBuf 第五节: directArena分配缓冲区概述 上一小节简单分析了PooledByteBufAllocator中, 线程局部缓存和arean的相关逻辑, 这 ...

  8. Netty源码分析第5章(ByteBuf)---->第6节: 命中缓存的分配

    Netty源码分析第6章: ByteBuf 第六节: 命中缓存的分配 上一小节简单分析了directArena内存分配大概流程, 知道其先命中缓存, 如果命中不到, 则区分配一款连续内存, 这一小节带 ...

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

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

随机推荐

  1. 学习java 7.21

    学习内容: 模块使用 AWT是窗口框架 它从不同平台的窗口系统中抽取出共同组件,当程序运行时,将这些组件的创建和动作委托给程序所在的运行平台.简而言之,当使用AWT编写图形界面应用时,程序仅指定了界面 ...

  2. apostrophe

    apostrophe 者,', 0x27, 十进制39,ASCII里的single quote (单引号) 也.one of the 'inverted commas'. 在书写上可以表示所有格.省略 ...

  3. day02 Rsyuc备份服务器

    day02 Rsyuc备份服务器 一.备份 1.什么是备份 备份就是把重要的数据或者文件复制一份保存到另一个地方,实现不同主机之间的数据同步 一般数据比较重要的情况下,数据如果丢失很容易找不回来了的, ...

  4. 26. Linux GIT

    windows git 下载链接: Msysgit   https://git-scm.com/download/win 1 进入git bash进行第一次配置 git config --global ...

  5. promise.all的应用场景举例

    Promise.all方法 简而言之:Promise.all( ).then( )适用于处理多个异步任务,且所有的异步任务都得到结果时的情况. 比如:用户点击按钮,会弹出一个弹出对话框,对话框中有两部 ...

  6. 【leetcode】378. Kth Smallest Element in a Sorted Matrix(TOP k 问题)

    Given an n x n matrix where each of the rows and columns is sorted in ascending order, return the kt ...

  7. ORACLE 本session产生的redo

    select * from v$statname a ,v$mystat bwhere a.STATISTIC# = b.STATISTIC# and a.name = 'redo size';

  8. Local Classes in C++

    A class declared inside a function becomes local to that function and is called Local Class in C++. ...

  9. Linux基础命令---exportfs管理挂载的nfs文件系统

    exportfs exportfs主要用于管理当前NFS服务器的文件系统. 此命令的适用范围:RedHat.RHEL.Ubuntu.CentOS.Fedora. 1.语法        /usr/sb ...

  10. 【编程思想】【设计模式】【结构模式Structural】门面模式/外观模式Facade

    Python版 https://github.com/faif/python-patterns/blob/master/structural/facade.py #!/usr/bin/env pyth ...