引用计数是一种常用的内存管理机制,是指将资源的被引用次数保存起来,当被引用次数变为零时就将其释放的过程。Netty在4.x版本开始使用引用计数机制进行部分对象的管理,其实现思路并不是特别复杂,它主要涉及跟踪某个对象被引用的次数。在Netty具体代码中需要通过引用计数进行内存管理的对象,会基于ReferenceCounted接口实现,其中引用计数大于0时则代表该对象被引用不会释放,当引用计数减少到0时,该对象就会被释放。通过引用计数机制,Netty可以很好的实现内存管理,引用计数减少到0时要么直接释放内存,要么放回内存池中重复利用。

1、基本示例

下面先通过一个简单示例看下Netty中引用计数机制的使用

  1. @Override
  2. public void channelRead(ChannelHandlerContext ctx, Object msg) {
  3.  
  4. ByteBuf recvBuffer = (ByteBuf) msg;// 申请ByteBuf 需要主动释放
  5. if(recvBuffer.isDirect()){
  6. System.err.println(true);
  7. }
  8. PooledByteBufAllocator allocator = new PooledByteBufAllocator(true);
  9. ByteBuf sendBuffer = allocator.buffer();//申请池化直接内存
  10. System.err.println("sendBuffer的引用计数:"+sendBuffer.refCnt());
  11. sendBuffer.retain();
  12. System.err.println("sendBuffer的引用计数:"+sendBuffer.refCnt());
  13. sendBuffer.release();
  14. System.err.println("sendBuffer的引用计数:"+sendBuffer.refCnt());

  15. try {
  16. byte[] bytesReady = new byte[recvBuffer.readableBytes()];
  17. recvBuffer.readBytes(bytesReady);
  18. System.out.println("channelRead收到数据:"+ BytesUtils.toHexString(bytesReady));
  19. byte[] sendBytes = new byte[] {0x7E,0x01,0x02,0x7e};
  20. sendBuffer.writeBytes(sendBytes);
  21. ctx.writeAndFlush(sendBuffer);
  22. System.err.println("sendBuffer的引用计数:"+sendBuffer.refCnt());
  23. }catch (Exception e) {
  24. // TODO: handle exception
  25. System.err.println(e.getMessage());
  26. }finally {
  27. System.err.println("recvBuffer的引用计数:"+recvBuffer.refCnt());
  28. recvBuffer.release(); //此处需要释放
  29. System.err.println("recvBuffer的引用计数:"+recvBuffer.refCnt());
  30. }
  31.  
  32. }

输出结果如下,通过示例可以看出retain方法会增加计数引用,release方法会减少计数引用

  1. true
  2. sendBuffer的引用计数:1
  3. sendBuffer的引用计数:2
  4. sendBuffer的引用计数:1
  5. sendBuffer的引用计数:0
  6. recvBuffer的引用计数:1
  7. recvBuffer的引用计数:0

AbstractReferenceCountedByteBuf实现了对ByteBuf的内存管理,以实现内存的回收、释放或者重复利用 ,AbstractReferenceCountedByteBuf的继承实现关系如下图所示

2、ReferenceCounted接口定义

首先是ReferenceCounted接口的定义

  1. public interface ReferenceCounted {
  2. /**
  3. * Returns the reference count of this object. If {@code 0}, it means this object has been deallocated.
  4. * 返回对象的引用计数
  5. */
  6. int refCnt();
  7.  
  8. /**
  9. * Increases the reference count by {@code 1}.
  10. * 增加引用计数
  11. */
  12. ReferenceCounted retain();
  13.  
  14. /**
  15. * Increases the reference count by the specified {@code increment}.
  16. * 引用计数增加指定值
  17. */
  18. ReferenceCounted retain(int increment);
  19.  
  20. /**
  21. * Records the current access location of this object for debugging purposes.
  22. * If this object is determined to be leaked, the information recorded by this operation will be provided to you
  23. * via {@link ResourceLeakDetector}. This method is a shortcut to {@link #touch(Object) touch(null)}.
  24. * 记录该对象的当前访问位置,用于调试。
  25. * 如果确定该对象被泄露,将提供此操作记录的信息给您
  26. */
  27. ReferenceCounted touch();
  28.  
  29. /**
  30. * Records the current access location of this object with an additional arbitrary information for debugging
  31. * purposes. If this object is determined to be leaked, the information recorded by this operation will be
  32. * provided to you via {@link ResourceLeakDetector}.
  33. * 记录该对象的当前访问位置,附加信息用于调试。
  34. * 如果确定该对象被泄露,将提供此操作记录的信息给您
  35. */
  36. ReferenceCounted touch(Object hint);
  37.  
  38. /**
  39. * Decreases the reference count by {@code 1} and deallocates this object if the reference count reaches at
  40. * {@code 0}.
  41. *
  42. * @return {@code true} if and only if the reference count became {@code 0} and this object has been deallocated
  43. * 引用计数减少,如果计数变为了0,则释放对象资源
  44. * 如果对象资源被释放,则返回true,否则返回false
  45. */
  46. boolean release();
  47.  
  48. /**
  49. * Decreases the reference count by the specified {@code decrement} and deallocates this object if the reference
  50. * count reaches at {@code 0}.
  51. *
  52. * @return {@code true} if and only if the reference count became {@code 0} and this object has been deallocated
  53. * 引用计数-指定值,如果计数变为了0,则释放对象资源或交回到对象池
  54. * 如果对象资源被释放,则返回true,否则返回false
  55. */
  56. boolean release(int decrement);
  57. }

3、AbstractReferenceCountedByteBuf源码分析

AbstractReferenceCountedByteBuf对ReferenceCounted进行了具体实现,retain与release两个方法通过CAS方式对引用计数refcnt进行操作,下面对其源码进行简单分析

初始化

引用计数初始值refCnt 使用关键字volatile修饰,保证线程的可见性,同时使用偶数,引用增加通过位移操作实现,提高运算效率。

采用 AtomicIntegerFieldUpdater 对象,通过CAS方式更新refCnt,以实现线程安全,避免加锁,提高效率。

  1. private static final long REFCNT_FIELD_OFFSET;
  2. //采用 AtomicIntegerFieldUpdater 对象,CAS方式更新refCnt
  3. private static final AtomicIntegerFieldUpdater<AbstractReferenceCountedByteBuf> refCntUpdater =
  4. AtomicIntegerFieldUpdater.newUpdater(AbstractReferenceCountedByteBuf.class, "refCnt");
  5.  
  6. //refCnt 实际值为偶数,采用位移操作提高效率
  7. // even => "real" refcount is (refCnt >>> 1); odd => "real" refcount is 0
  8. @SuppressWarnings("unused")
  9. private volatile int refCnt = 2;

retain操作

上面示例中每调用一次retain方法,引用计数就会累加一次,我们看下源码中retain的具体实现

  1. public ByteBuf retain() {
  2. return retain0(1);
  3. }
  4.  
  5. @Override
  6. public ByteBuf retain(int increment) {
  7. return retain0(checkPositive(increment, "increment"));
  8. }
  9.  
  10. //计数器增值操作
  11. private ByteBuf retain0(final int increment) {
  12. // all changes to the raw count are 2x the "real" change
  13. int adjustedIncrement = increment << 1; // overflow OK here 真正的计数都是2倍递增
  14. int oldRef = refCntUpdater.getAndAdd(this, adjustedIncrement); //通过CAS方式递增并获取原值
  15. if ((oldRef & 1) != 0) {//判断奇偶,正常情况这里应该都是偶数
  16. throw new IllegalReferenceCountException(0, increment);
  17. }
  18. // don't pass 0! 如果计数小于等于0,以及整型范围越界(0x7fffffff+1)抛出异常
  19. if ((oldRef <= 0 && oldRef + adjustedIncrement >= 0)
  20. || (oldRef >= 0 && oldRef + adjustedIncrement < oldRef)) {
  21. // overflow case
  22. refCntUpdater.getAndAdd(this, -adjustedIncrement);
  23. throw new IllegalReferenceCountException(realRefCnt(oldRef), increment);
  24. }
  25. return this;
  26. }

release操作

通过调用release方法,对引用计数做减值操作,源码中release的具体实现要注意的是由于引用计数以2倍递增,所以引用次数= 引用计数/2,当decrement=refcnt/2 也就是引用次数=释放次数时,代表ByteBuf不再被引用,执行内存释放或放回内存池的操作。

  1. //计数器减值操作
  2. private boolean release0(int decrement) {
  3. int rawCnt = nonVolatileRawCnt(), realCnt = toLiveRealCnt(rawCnt, decrement); //对计数器进行除以2操作,也就是引用次数
  4. /**
  5. * /这里如注意 你传入的减值参数decrement = realCnt 时 等同于 引用次数=释放次数,直接进行释放操作
  6. */
  7. if (decrement == realCnt) {
  8. if (refCntUpdater.compareAndSet(this, rawCnt, 1)) { //CAS方式置为1
  9. deallocate();//内存释放或放回内存池
  10. return true;
  11. }
  12. return retryRelease0(decrement);//进入具体操作
  13. }
  14. return releaseNonFinal0(decrement, rawCnt, realCnt);
  15. }
  16.  
  17. private boolean releaseNonFinal0(int decrement, int rawCnt, int realCnt) {
  18. //如果decrement 小于 realCnt,通过CAS方式减去decrement*2
  19. if (decrement < realCnt
  20. // all changes to the raw count are 2x the "real" change
  21. && refCntUpdater.compareAndSet(this, rawCnt, rawCnt - (decrement << 1))) {
  22. return false;
  23. }
  24. return retryRelease0(decrement);
  25. }
  26.  
  27. private boolean retryRelease0(int decrement) {
  28. for (;;) {
  29. int rawCnt = refCntUpdater.get(this), realCnt = toLiveRealCnt(rawCnt, decrement);
  30. if (decrement == realCnt) {
  31. if (refCntUpdater.compareAndSet(this, rawCnt, 1)) {
  32. deallocate();
  33. return true;
  34. }
  35. } else if (decrement < realCnt) {//如果decrement 小于 realCnt,通过CAS方式减去decrement*2
  36. // all changes to the raw count are 2x the "real" change
  37. if (refCntUpdater.compareAndSet(this, rawCnt, rawCnt - (decrement << 1))) {
  38. return false;
  39. }
  40. } else {
  41. throw new IllegalReferenceCountException(realCnt, -decrement);
  42. }
  43. Thread.yield(); // this benefits throughput under high contention
  44. }
  45. }
  46.  
  47. /**
  48. * Like {@link #realRefCnt(int)} but throws if refCnt == 0
  49. */
  50. private static int toLiveRealCnt(int rawCnt, int decrement) {
  51. if ((rawCnt & 1) == 0) {
  52. return rawCnt >>> 1;
  53. }
  54. // odd rawCnt => already deallocated
  55. throw new IllegalReferenceCountException(0, -decrement);
  56. }

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. 入坑不亏!我们最终决定将 70w+ 核心代码全部开源

    作者 | 一啸 来源 | 尔达 Erda 公众号 背景故事 2017 年初,我们基于 DC/OS (mesos + marathon) 开始构建端点自己的 PaaS 平台,核心任务就是解决公司的软件开 ...

  2. 【leetcode】36. Valid Sudoku(判断能否是合法的数独puzzle)

    Share Determine if a 9 x 9 Sudoku board is valid. Only the filled cells need to be validated accordi ...

  3. CVTE第二次笔试

    选择瞎答得,直接编程题目 1. 使用递归将字符串中的数字去并按顺序打印 输入例  adfsafsfs123123eogie09789 输出例 123123 09789 #include<iost ...

  4. GO 定时器NewTimer、NewTicker使用

    package main import ( "fmt" "sync" "time" ) /** *ticker只要定义完成,从此刻开始计时, ...

  5. Advanced C++ | Virtual Copy Constructor

    这个不懂,等看会了再写...

  6. ES安装简记

    JDK # java -versionjava version "1.8.0_231"Java(TM) SE Runtime Environment (build 1.8.0_23 ...

  7. 【Services】【Web】【tomcat】配置tomcat支持https传输

    1. 基础: 1.1. 描述:内网的tomcat接到外网nginx转发过来的请求之后需要和外网的客户端进行通讯,为了保证通讯内容的安装,使用tomcat使用https协议. 1.2. 链接:http: ...

  8. 初步接触Linux命令

    目录 虚拟机快照 1.首先将已经运行的系统关机 2.找到快照 拍摄快照 3.找到克隆 下一步 有几个快照会显示几个 4.克隆完成后 要修改一下IP 不然无法同时运行两个虚拟机系统 系统介绍 1.pin ...

  9. 过滤敏感词工具类SensitiveFilter

    网上过滤敏感词工具类有的存在挺多bug,这是我自己改用的过滤敏感词工具类,目前来说没啥bug,如果有bug欢迎在评论指出 使用前缀树 Trie 实现的过滤敏感词,树节点用静态内部类表示了,都写在一个 ...

  10. 4、Redis基础

    redis性能 1.关于测试性能 官方自带的测试性能的工具 redis-benchmark 压力测试工具 #进行压力测试.需求:测试:100个并发连接,100000个请求 #redis-benchma ...