从Netty 4起,对象的生命周期由它们的引用计数来管理,因此,一旦对象不再被引用后,Netty 会将它(或它共享的资源)归还到对象池(或对象分配器)。在垃圾回收和引用队列不能保证这么有效、实时的不可达性检测的情况下,引用计数以牺牲轻微的便利性为代价,提供了另一种可选的解决方案。 最值得注意的类型是ByteBuf,它正是利用了引用计数来提升内存分配和释放的性能

一、引用计数基本原理

一个新创建的引用计数对象的初始引用计数是1。

ByteBuf buf = ctx.alloc().directbuffer();
assert buf.refCnt() == 1;

当你释放掉引用计数对象,它的引用次数减1.如果一个对象的引用计数到达0,该对象就会被 释放或者归还到创建它的对象池。

assert buf.refCnt() == 1;
// release() returns true only if the reference count becomes 0.
boolean destroyed = buf.release();
assert destroyed;
assert buf.refCnt() == 0;
  1. 悬挂引用

访问引用计数为0的引用计数对象会触发一次IllegalReferenceCountException

assert buf.refCnt() == 0;
try {buf.writeLong(0xdeadbeef);
throw new Error("should not reach here");
} catch (IllegalReferenceCountExeception e) {// Expected
}
  1. 增加引用计数

只要引用计数对象未被销毁,就可以通过调用retain()方法来增加引用次数

ByteBuf buf = ctx.alloc().directBuffer();
assert buf.refCnt() == 1;
buf.retain();
assert buf.refCnt() == 2;
boolean destroyed = buf.release();
assert !destroyed;
assert buf.refCnt() == 1;
  1. 谁来销毁
    一般的原则是,最后访问引用计数对象的部分负责对象的销毁。更具体地来说:
  • 如果一个[发送]组件要传递一个引用计数对象到另一个[接收]组件,发送组件通常不需要 负责去销毁对象,而是将这个销毁的任务推延到接收组件
  • 如果一个组件消费了一个引用计数对象,并且不知道谁会再访问它(例如,不会再将引用 发送到另一个组件),那么,这个组件负责销毁工作
    这里有一个简单的示例:
public ByteBuf a(ByteBuf input) {input.writeByte(42);return input;
}
public ByteBuf b(ByteBuf input) {try {output = input.alloc().directBuffer(input.readableBytes() + 1);output.writeBytes(input);output.writeByte(42);return output;} finally {input.release();}
}
public void c(ByteBuf input) {System.out.println(input);input.release();
}
public void main() {...ByteBuf buf = ...;// This will print buf to System.out and destroy it.c(b(a(buf)));assert buf.refCnt() == 0;
}

二、子缓冲区(Derived buffers)

调用ByteBuf.duplicate(),ByteBuf.slice()和ByteBuf.order(ByteOrder)三个方法, 会创建一个子缓冲区,子缓冲区共享父缓冲区的内存区域。子缓冲区没有自己的引用计数,而是共享父缓冲区的引用计数。

ByteBuf parent = ctx.alloc().directBuffer();
ByteBuf derived = parent.duplicate();
// Creating a derived buffer does not increase the reference count.
assert parent.refCnt() == 1;
assert derived.refCnt() == 1;

但是,调用ByteBuf.copy()和ByteBuf.readBytes(int)创建的并不是子缓冲区,返回的 ByteBuf缓冲区是需要被释放的。 需要注意,父缓冲区和它的子缓冲区共享引用计数,创建子缓冲区并不会增加引用计数。 因此,当你将子缓冲区传到应用中的其他组件,必须先调用retain()。

ByteBuf parent = ctx.alloc().directBuffer(512);
parent.writeBytes(...);
try {while (parent.isReadable(16)) {ByteBuf derived = parent.readSlice(16);derived.retain();process(derived);}
} finally {parent.release();
}
...
public void process(ByteBuf buf) {...buf.release();
}
  1. ByteBufHolder接口
    有时候,ByteBuf被缓冲区容器(buffer holder)持有,像DatagramPacket、HttpComponent和WebSocketFrame。 这些类型都继承自一个通用接口,叫做ByteBufHolder。 缓冲区容器(buffer holder)共享它持有的缓冲区的引用计数,和子缓冲区一样。

  2. Channel-handler中的引用计数

  • 入口消息

当一个事件循环(event loop)读取数据并写入到ByteBuf,在触发一次channelRead()事件后,应该由对应pipeline的 ChannelHandler负责去释放缓冲区的内存。因此,消费接收数据的handler应该在它channelRead()方法中调用数据的 release()方法。

public void channelRead(ChannelHandlerContext ctx, Object msg) {ByteBuf buf = (ByteBuf) msg;try {...} finally {buf.release();}}

在“谁负责销毁”一节中我们提到,如果你的handler将缓冲区(或者其他任何引用计数对象)传递到下一个handler, 那么你不需要负责去释放。

public void channelRead(ChannelHandlerContext ctx, Object msg) {ByteBuf buf = (ByteBuf) msg;...ctx.fireChannelRead(buf);
}

需要注意的是,ByteBuf并不是Netty中唯一的引用计数类型。如果你在与解码程序(decoder)生成的消息打交道,这些消息一样可能 是引用计数的。

/ Assuming your handler is placed next to `HttpRequestDecoder`
public void channelRead(ChannelHandlerContext ctx, Object msg) {if (msg instanceof HttpRequest) {HttpRequest req = (HttpRequest) msg;...}if (msg instanceof HttpContent) {HttpContent content = (HttpContent) msg;try {...} finally {content.release();}}
}

如果你有疑虑,或者你想简化释放消息内存的过程,你可以使用ReferenceCountUtil.release():

public void channelRead(ChannelHandlerContext ctx, Object msg) {try {...} finally {ReferenceCountUtil.release(msg);}
}

同样地,你可以考虑继承SimpleChannelHandler,它会帮你调用ReferenceCountUtil.release()释放所有 你接收到的消息内存。

  • 出口消息

与入口消息不同的是,出口消息是在你的应用中创建的,由Netty负责在将消息发送出去后释放掉。但是,如果你 有拦截写请求的handler程序,则需要保证正确释放中间对象(例如,编码程序)。

public void write(ChannelHandlerContext ctx, Object message, ChannelPromise promise) {System.err.println("Writing: " + message);ctx.write(message, promise);
}
// Transformation
public void write(ChannelHandlerContext ctx, Object message, ChannelPromise promise) {if (message instanceof HttpContent) {// Transform HttpContent to ByteBuf.HttpContent content = (HttpContent) message;try {ByteBuf transformed = ctx.alloc().buffer();....ctx.write(transformed, promise);} finally {content.release();}} else {// Pass non-HttpContent through.ctx.write(message, promise);}
}

三、内存泄漏

引用计数的缺点是,引用计数对象容易发生泄露。因为JVM并不知道Netty的引用计数实现,当引用计数对象不可达时,JVM就会将它们GC掉,即时此时它们的引用计数并不为0。一旦对象被GC就不能再访问,也就不能 归还到缓冲池,所以会导致内存泄露。 庆幸的是,尽管发现内存泄露很难,但是Netty会对分配的缓冲区的1%进行采样,来检查你的应用中是否存在内存 泄露。

文章转自

Netty的引用计数对象相关推荐

  1. 【Netty4】netty ByteBuf (二) 引用计数对象(reference counted objects)

    原文出处:http://netty.io/wiki/reference-counted-objects.html 相关文章: netty ByteBuf (一)如何创建ByteBuf对象 netty ...

  2. 【Netty官方文档翻译】引用计数对象(reference counted objects)

    原文出处:http://netty.io/wiki/reference-counted-objects.html 原文地址可能有变,且内容可能发生变化. 如果转载请注明出处,谢谢合作^_^. 自从Ne ...

  3. netty 引用计数对象(reference counted objects)

    [Netty官方文档翻译]引用计数对象(reference counted objects) http://damacheng009.iteye.com/blog/2013657 转载于:https: ...

  4. Reference counted Objects (引用计数对象) - 文章翻译

    原文地址:http://netty.io/wiki/reference-counted-objects.html 从Netty4开始,某些对象的饿生命周期由其引用计数来管理,因此,一旦不再使用,Net ...

  5. python学习高级篇(part9)--对象的引用计数

    学习笔记,仅供参考,有错必纠 文章目录 python 学习高级篇 类对象的特殊方法之`__str__()` 类对象的特殊方法之`__new__()` 对象的引用计数 什么是引用计数 对象的引用计数加1 ...

  6. 提高C++性能的编程技术笔记:引用计数+测试代码

    引用计数(reference counting):基本思想是将销毁对象的职责从客户端代码转移到对象本身.对象跟踪记录自身当前被引用的数目,在引用计数达到零时自行销毁.换句话说,对象不再被使用时自行销毁 ...

  7. wxWidgets:引用计数

    wxWidgets:引用计数 wxWidgets:引用计数 对象比较 对象销毁 引用计数类列表 制作你自己的引用计数类 wxWidgets:引用计数 许多 wxWidgets 对象使用一种称为引用计数 ...

  8. c++ reference counting引用计数原理

    我们都知道,c++最令人头疼的问题也是被其他语言鄙视的问题--指针管理.而引用技术能够让上面的简化了不少.下面说说c++引用计数的设计原理. 引用计数的两个动机: 1. 简化heap对象周边的记录工作 ...

  9. Rust for Linux 源码导读 | Ref 引用计数容器 原创

    引子 2022 年,我们很可能会看到 Linux 内核中的实验性 Rust 编程语言支持成为主流.2021.12.6 早上发出了更新的补丁,介绍了在内核中处理 Rust 的初始支持和基础设施. 这次更 ...

最新文章

  1. 计算机电缆djyvp工艺,计算机电缆dJyvP相关办法.pdf
  2. C++的this指针和引用符号的搭配使用理解
  3. Citrix Xendesktop中VDA注册DDC的流程
  4. 数据库事务原理详解-事务基本概念
  5. 《大数据,小时代,向移动互联网迁徙-2012上半年移动互联网数据分享》_DCCI
  6. 镜像镜像–使用反射在运行时查看JVM内部
  7. mvc5控制器修改html,关于jquery:如何通过对控制器的ajax调用在MVC5中呈现局部视图并返回HTML...
  8. 数据科学 IPython 笔记本 9.7 数组上的计算:广播
  9. 【投放算法】“喵糖”背后的商业化流量投放算法应用及实践
  10. 《linux核心应用命令速查》连载十二:top:显示进程
  11. vbs代码弹计算机,如何恶搞朋友的电脑?超简单的vbs代码
  12. 于歆杰pdf 电路原理_电路原理于歆杰答案pdf
  13. 3.3.3 使用集线器的星形拓补
  14. mysql通过Navcat 备份数据.psc 还原数据时 只有表没有数据解决方法
  15. 短视频SDK接入(2)---环境搭建
  16. 2022年装饰行业市场分析
  17. 微信SDK中含有的支付功能怎么去掉?
  18. C语言之负数的左移/右移运算
  19. css的几种布局方式都在这
  20. 学习vue笔记(5)

热门文章

  1. 常见的加密和解密算法—MD5
  2. Android的BroadcastReceiver 广播 短信拦截
  3. C++ 对象内存布局 (4)
  4. Delphi编程注意事项
  5. chapter 15 运算符详解
  6. 深度之眼课程打卡-统计学习方法01
  7. Python学习笔记:Import详解2
  8. Python学习笔记:入门(2)
  9. Java中resource文件夹
  10. 【随笔】深度学习的数据增强还分在线和离线?