原文出处:http://netty.io/wiki/reference-counted-objects.html

相关文章:
netty ByteBuf (一)如何创建ByteBuf对象
netty ByteBuf (二) 引用计数对象(reference counted objects)
netty ByteBuf(三)如何释放ByteBuf

自从Netty 4开始,对象的生命周期由它们的引用计数(reference counts)管理,而不是由垃圾收集器(garbage collector)管理了。ByteBuf是最值得注意的,它使用了引用计数来改进分配内存和释放内存的性能。

下文中的测试例子主要是断言为主,elcipse开启断言添加-ea即可

基本的引用计数

每个对象的初始计数为1

public class Test {public static void main(String[] args) {ByteBuf buf = Unpooled.buffer(10);assert buf.refCnt() == 1;

当你释放(release)引用计数对象时,它的引用计数减1.如果引用计数为0,这个引用计数对象会被释放(deallocate),并返回对象池

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

注意:只有引用计数变为0时,release()方法才返回true

悬垂(dangling)引用

尝试访问引用计数为0的引用计数对象会抛出IllegalReferenceCountException异常:

assert buf.refCnt() == 0;
try {  buf.writeLong(0xdeadbeef);  throw new Error("should not reach here");
} catch (IllegalReferenceCountExeception e) {  // Expected
}

增加引用计数

可通过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;

谁来销毁(destroy)

通常的经验法则是谁最后访问(access)了引用计数对象,谁就负责销毁(destruction)它。具体来说是以下两点:

  • 如果组件(component)A把一个引用计数对象传给另一个组件B,那么组件A通常不需要销毁对象,而是把决定权交给组件B。
  • 如果一个组件消费了一个引用计数对象并且知道不会再有其他组件访问这个对象,那么这个组件负责销毁它。
  public static ByteBuf a(ByteBuf input) {input.writeByte(42);return input;}public static ByteBuf b(ByteBuf input) {try {ByteBuf output = input.alloc().directBuffer(input.readableBytes() + 1);output.writeBytes(input);output.writeByte(42);return output;} finally {input.release();}}public static void c(ByteBuf input) {System.out.println(input);input.release();}public static void main(String[] args) {ByteBuf buf = Unpooled.buffer(10);// This will print buf to System.out and destroy it.c(b(a(buf)));assert buf.refCnt() == 0;}
Action 谁应该释放?(左侧是资源,右侧是责任方) 谁释放了?(为空,表示当前责任方并没有实际释放行为,一般是转移了负责权至其他组件了)
1. main() 创建了buf bufmain()
2. main() 调用a() 并传入 buf bufa()
3. a() 仅仅返回buf bufmain()
4. main() 调用b() 并传入 buf bufb()
5. b() 返回buf的拷贝 bufb(), copymain() b() releases buf
6. main() 调用c() 并传入copy copyc()
7. c() 吞掉copy copyc() c() releases copy

子缓冲(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();
}

ByteBufHolder接口

有时候,一个ByteBuf被一个buffer holder持有,诸如DatagramPacket, HttpContent,和WebSocketframe。它们都扩展了一个公共接口,ByteBufHolder。

一个buffer holder共享它所持有的引用计数,如同子缓冲一样。

ChannelHandler中的引用计数

Inbound消息(messages)

当一个事件循环(event loop)读入了数据,用读入的数据创建了ByteBuf,并用这个ByteBuf触发了一个channelRead()事件时,那么管道(pipeline)中相应的ChannelHandler就负责释放这个buffer。因此,处理接收到的数据的handler应该在它的channelRead()中调用bufferrelease()

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)生成的消息(messages)对象,这些对象很可能也是引用计数对象:

// Assuming your handler is placed next to `HttpRequestDecoder`
public class  MyHandler extends ChannelInboundHandlerAdapter
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();}}
}

上面代码例子中,是假定pipeline 链为HttpRequestDecoder,MyHandler,消息先传入HttpRequestDecoder,然后再传入MyHandler,此时,传入MyHandler中的msg已经是一个对象了,后面也需要调用release()明文释放。

如果你抱有疑问,或者你想简化这些释放消息的工作,你可以使用ReferenceCountUtil.release():

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

还有一种选择,你可以考虑继承SimpleChannelHandler,它在所有接收消息的地方都调用了ReferenceCountUtil.release(msg)。

Outbound消息(messages)

与inbound消息不同,你的程序所创建的消息对象,由Netty负责释放,释放的时机是在这些消息被发送到网络之后。但是,在发送消息的过程中,如果有handler截获(intercept)了你的发送请求,并创建了一些中间对象,则这些handler要确保正确释放这些中间对象。比如编码器(encoder)。

// Simple-pass through
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);  }
}
解决(troubleshooting)buffer泄露

引用计数的缺点是容易发生泄露。因为JVM并不知道Netty实现的引用计数的存在,一旦某些对象不可达(unreachable)就会被自动GC掉,即使这些对象的引用计数不为0。被GC掉的对象就不可用了,因此这些对象也就不能回到对象池中,或者产生内存泄露。

幸运的是,尽管要找到泄露很困难,但Netty提供了一种方案来帮助发现泄露,此方案默认在你的程序中的已分配的缓冲中取样(sample)大约1%的缓存,来检查是否存在泄露。如果存在泄露,你会发现如下日志:

LEAK: ByteBuf.release() was not called before it's garbage-collected. Enable advanced leak reporting to find out where the leak occurred. To enable advanced leak reporting, specify the JVM option '-Dio.netty.leakDetectionLevel=advanced' or call ResourceLeakDetector.setLevel()

上述日志中提到的JVM选项(option)重新启动你的程序,你可以看到在你的程序中最近访问已泄露的内存的位置(location)。也就是说默认日志参数比较简单,不方便查找问题,而通过修改JVM 选项 ‘-Dio.netty.leakDetectionLevel=advanced’ 或调用直接在代码中调用API ResourceLeakDetector.setLevel()来修改日志级别,打印详细日志。
下列输出展示了来自单元测试的一个泄露问题(XmlFrameDecoderTest.testDecodeWithXml()):

Running io.netty.handler.codec.xml.XmlFrameDecoderTest
15:03:36.886 [main] ERROR io.netty.util.ResourceLeakDetector - LEAK: ByteBuf.release() was not called before it's garbage-collected.
Recent access records: 1
#1:  io.netty.buffer.AdvancedLeakAwareByteBuf.toString(AdvancedLeakAwareByteBuf.java:697)  io.netty.handler.codec.xml.XmlFrameDecoderTest.testDecodeWithXml(XmlFrameDecoderTest.java:157)  io.netty.handler.codec.xml.XmlFrameDecoderTest.testDecodeWithTwoMessages(XmlFrameDecoderTest.java:133)  ...  Created at:  io.netty.buffer.UnpooledByteBufAllocator.newDirectBuffer(UnpooledByteBufAllocator.java:55)  io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:155)  io.netty.buffer.UnpooledUnsafeDirectByteBuf.copy(UnpooledUnsafeDirectByteBuf.java:465)  io.netty.buffer.WrappedByteBuf.copy(WrappedByteBuf.java:697)  io.netty.buffer.AdvancedLeakAwareByteBuf.copy(AdvancedLeakAwareByteBuf.java:656)  io.netty.handler.codec.xml.XmlFrameDecoder.extractFrame(XmlFrameDecoder.java:198)  io.netty.handler.codec.xml.XmlFrameDecoder.decode(XmlFrameDecoder.java:174)  io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:227)  io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:140)  io.netty.channel.ChannelHandlerInvokerUtil.invokeChannelReadNow(ChannelHandlerInvokerUtil.java:74)  io.netty.channel.embedded.EmbeddedEventLoop.invokeChannelRead(EmbeddedEventLoop.java:142)  io.netty.channel.DefaultChannelHandlerContext.fireChannelRead(DefaultChannelHandlerContext.java:317)  io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:846)  io.netty.channel.embedded.EmbeddedChannel.writeInbound(EmbeddedChannel.java:176)  io.netty.handler.codec.xml.XmlFrameDecoderTest.testDecodeWithXml(XmlFrameDecoderTest.java:147)  io.netty.handler.codec.xml.XmlFrameDecoderTest.testDecodeWithTwoMessages(XmlFrameDecoderTest.java:133)  ...

如果你使用Netty 5或以上的版本,还提供了一个额外的信息,帮助我们找到最后操作了(handle)泄露缓冲的handler。下面的例子展示了名为EchoServerHandler#0的handler操作了已泄露的缓冲,并且缓冲已被GC了,这意味着EchoServerHandler#0忘记释放了这个buffer:

12:05:24.374 [nioEventLoop-1-1] ERROR io.netty.util.ResourceLeakDetector - LEAK: ByteBuf.release() was not called before it's garbage-collected.
Recent access records: 2
#2:  Hint: 'EchoServerHandler#0' will handle the message from this point.  io.netty.channel.DefaultChannelHandlerContext.fireChannelRead(DefaultChannelHandlerContext.java:329)  io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:846)  io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:133)  io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:485)  io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:452)  io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:346)  io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:794)  java.lang.Thread.run(Thread.java:744)
#1:  io.netty.buffer.AdvancedLeakAwareByteBuf.writeBytes(AdvancedLeakAwareByteBuf.java:589)  io.netty.channel.socket.nio.NioSocketChannel.doReadBytes(NioSocketChannel.java:208)  io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:125)  io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:485)  io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:452)  io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:346)  io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:794)  java.lang.Thread.run(Thread.java:744)
Created at:  io.netty.buffer.UnpooledByteBufAllocator.newDirectBuffer(UnpooledByteBufAllocator.java:55)  io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:155)  io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:146)  io.netty.buffer.AbstractByteBufAllocator.ioBuffer(AbstractByteBufAllocator.java:107)  io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:123)  io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:485)  io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:452)  io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:346)  io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:794)  java.lang.Thread.run(Thread.java:744)

泄露检测级别

当前有4个泄露检测级别:

  • 禁用(DISABLED) - 完全禁止泄露检测。不推荐。
  • 简单(SIMPLE) - 告诉我们取样的1%的缓冲是否发生了泄露。默认。
  • 高级(ADVANCED) - 告诉我们取样的1%的缓冲发生泄露的地方
  • 偏执(PARANOID) - 跟高级选项类似,但此选项检测所有缓冲,而不仅仅是取样的那1%。此选项在自动测试阶段很有用。如果构建(build)输出包含了LEAK,可认为构建失败。

你可以使用JVM的-Dio.netty.leakDetectionLevel选项来指定泄漏检测级别。

避免泄露的最佳实践

  • 在简单级别和偏执级别上运行你的单元测试和集成测试(integration tests)。
  • 在rolling out到整个集群之前,使用简单级别,以一个合理的、足够长的时间canary(金丝雀?不明所以。。)你的程序,来发现是否存在泄露。
  • 如果存在泄露,再用高级级别来canary以获得一些关于泄露的提示。
  • 不要部署存在泄露的程序到整个集群。

在单元测试中修复泄露问题

在单元测试中很容易忘记释放缓冲。这会产生一个泄露的警告,但并不是说就肯定存在泄露。你可以使用ReferenceCountUtil.releaseLater()工具方法,放弃用try-finally来包裹你的单元测试代码以释放所有的缓冲:

import static io.netty.util.ReferenceCountUtil.*;  @Test
public void testSomething() throws Exception {  // ReferenceCountUtil.releaseLater() will keep the reference of buf,// and then release it when the test thread is terminated.ByteBuf buf = releaseLater(Unpooled.directBuffer(512));  ...
}

【Netty4】netty ByteBuf (二) 引用计数对象(reference counted objects)相关推荐

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

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

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

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

  3. Netty的引用计数对象

    从Netty 4起,对象的生命周期由它们的引用计数来管理,因此,一旦对象不再被引用后,Netty 会将它(或它共享的资源)归还到对象池(或对象分配器).在垃圾回收和引用队列不能保证这么有效.实时的不可 ...

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

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

  5. 引用计数(Reference Counting)和代理(Proxy)的应用

    引子 如果让你用C++写一个实用的字符串类,我想下面的方案是很多人最先想到的: class ClxString { public: ClxString(); ClxString(const char ...

  6. C++引用计数(reference counting)技术简介(3)

    1.将Reference Counting加到既有的Class 要想将引用计数施加到现有的实值对象Widget上,按照前面讨论的,都需要修改Winget类的源代码.但是,有时程序库的内容不是我们呢可以 ...

  7. C++引用计数(reference counting)技术简介(2)

    1.一个引用计数(Reference-Counting)基类 Reference-counting可用于字符串以外的场合,任何class如果其不同的对象可能拥有相同的值,都适用此技术.但是如果重写cl ...

  8. Netty的深入浅出--79.Netty官方Reference Counted Objects文档说明

    netty4之后通过确定的一个the life cycle of certain object 来管理reference counts,一但不在被使用的时候返回一个object pool 简单来说就是 ...

  9. 深入理解JVM03--判断对象是否存活(引用计数算法、可达性分析算法,最终判定),Eclipse设置GC日志输出,引用

    本文是基于周志明的<深入理解Java虚拟机> 堆中几乎存放着Java世界中所有的对象实例,垃圾收集器在对堆回收之前,第一件事情就是要确定这些对象哪些还"存活"着,哪些对 ...

最新文章

  1. 用 go 实现跨平台 Autoit/AutoHotkey 和按键精灵功能示例代码
  2. go 函数参数nil_深入理解 Go-Defer的机制
  3. 动态管理配置文件扩展接口EnvironmentPostProcessor
  4. 限时免费下载丨《2021 中国游戏市场挑战与机遇盘点》重磅发布!
  5. 电脑间用网线传输文件的方法Win10-Win10(Win7)
  6. [置顶] 完美程序员的10种品质
  7. java ranger rest_使用REST
  8. 【记忆化搜索】bzoj3208 花神的秒题计划Ⅰ
  9. 2000元档855旗舰来了 网友:都过时了,哪有人买
  10. AWS上的实例无法ping通的解决方案
  11. centos 6.5卸载Mysql
  12. python程序设计方法学_python学习笔记(12)--程序设计方法学
  13. 某公司的雇员分为以下若干类: Employee:这是所有员工总的父类.属性:员工的姓名,员工的生日月份。 方法:getSalary(intmonth)
  14. python bytes转str_Python3中bytes类型转换为str类型
  15. 吃冬瓜对宝宝有什么好处?
  16. 【每周一本书】之《大数据核心技术与实用算法》
  17. Linux 网络编程-进程管道
  18. fluent p1模型_FLUENT中的辐射模型
  19. 成功解决: Windows10没有蓝牙问题
  20. 冲破百亿天花板,木浪云用云和智能突破备份边界

热门文章

  1. 乐视2 usb计算机连接,乐视 LetvX620 开启USB调试模式
  2. Intel Xeon Cooper Lake处理器CPU主频睿频性能详解
  3. 如何挽救婚姻?不想离婚就做好这8个方面,分分钟留下她
  4. 统计信号处理基础 习题解答5-8
  5. 【nginx】4xx,5xx 保持自定义header
  6. LINK : fatal error LNK1104: 无法打开文件“mfc71.lib”的原因又一例
  7. c语言tab什么意思_C语言入门学好这一篇就足够了!C语言所有的知识点
  8. android高仿微信的图片查看
  9. 毫米和像素怎么换算_图片的像素和毫米之间是怎么换算的有公式吗
  10. 苹果手机上音乐播放的问题