【Netty官方文档翻译】引用计数对象(reference counted objects)
原文出处:http://netty.io/wiki/reference-counted-objects.html
原文地址可能有变,且内容可能发生变化。
如果转载请注明出处,谢谢合作^_^。
自从Netty 4开始,对象的生命周期由它们的引用计数(reference counts)管理,而不是由垃圾收集器(garbage collector)管理了。ByteBuf是最值得注意的,它使用了引用计数来改进分配内存和释放内存的性能。
基本的引用计数
每个对象的初始计数为1:
- ByteBuf buf = ctx.alloc().directBuffer();
- assert buf.refCnt() == 1;
当你释放(release)引用计数对象时,它的引用计数减1.如果引用计数为0,这个引用计数对象会被释放(deallocate),并返回对象池。
- assert buf.refCnt() == 1;
- // release() returns true only if the reference count becomes 0.
- boolean destroyed = buf.release();
- assert destroyed;
- assert buf.refCnt() == 0;
悬垂(dangling)引用
尝试访问引用计数为0的引用计数对象会抛出IllegalReferenceCountException异常:
- assert buf.refCnt() == 0;
- try {
- buf.writeLong(0xdeadbeef);
- throw new Error("should not reach here");
- } catch (IllegalReferenceCountExeception e) {
- // Expected
- }
增加引用计数
可通过retain()操作来增加引用计数,前提是此引用计数对象未被销毁:
(译者注:跟未使用ARC的objective-c好像)
- 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 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;
- }
行为(Action) 谁来释放(Who should release)? 谁释放了(Who released)?
1. main()创建了buf buf→main()
2. buf由main()传给了a() buf→a()
3. a()仅仅返回了buf buf→main()
4. buf由main()传给了b() buf→b()
5. b()返回了buf的拷贝 buf→b(), copy→main() b()释放了buf
6. 拷贝由main()传给了c() copy→c()
7. c()消耗(swallow)了拷贝 copy→c() c()释放了拷贝
子缓冲(Derived buffers)
ByteBuf.duplicate(), ByteBuf.slice()和ByteBuf.order(ByteOrder)创建了子缓冲,这些缓存共享了它们的父缓冲(parent buffer)的一部分内存。子缓冲没有自己的引用计数,而是共享父缓冲的引用计数。
- 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;
注意父缓冲和它的子缓冲共享同样的引用计数,当创建子缓冲时并不会增加对象的引用计数。因此,如果你要传递(pass)一个子缓冲给你的程序中的其他组件的话,你得先调用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()中调用buffer的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)生成的消息(messages)对象,这些对象很可能也是引用计数对象:
- // 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(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)。下列输出展示了来自单元测试的一个泄露问题(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选项来指定泄漏检测级别。
- java -Dio.netty.leakDetectionLevel=advanced ...
避免泄露的最佳实践
- 在简单级别和偏执级别上运行你的单元测试和集成测试(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));
- ...
- }
【Netty官方文档翻译】引用计数对象(reference counted objects)相关推荐
- 【Netty4】netty ByteBuf (二) 引用计数对象(reference counted objects)
原文出处:http://netty.io/wiki/reference-counted-objects.html 相关文章: netty ByteBuf (一)如何创建ByteBuf对象 netty ...
- netty 引用计数对象(reference counted objects)
[Netty官方文档翻译]引用计数对象(reference counted objects) http://damacheng009.iteye.com/blog/2013657 转载于:https: ...
- Reference counted Objects (引用计数对象) - 文章翻译
原文地址:http://netty.io/wiki/reference-counted-objects.html 从Netty4开始,某些对象的饿生命周期由其引用计数来管理,因此,一旦不再使用,Net ...
- Netty的引用计数对象
从Netty 4起,对象的生命周期由它们的引用计数来管理,因此,一旦对象不再被引用后,Netty 会将它(或它共享的资源)归还到对象池(或对象分配器).在垃圾回收和引用队列不能保证这么有效.实时的不可 ...
- 引用计数(Reference Counting)和代理(Proxy)的应用
引子 如果让你用C++写一个实用的字符串类,我想下面的方案是很多人最先想到的: class ClxString { public: ClxString(); ClxString(const char ...
- C++引用计数(reference counting)技术简介(3)
1.将Reference Counting加到既有的Class 要想将引用计数施加到现有的实值对象Widget上,按照前面讨论的,都需要修改Winget类的源代码.但是,有时程序库的内容不是我们呢可以 ...
- C++引用计数(reference counting)技术简介(2)
1.一个引用计数(Reference-Counting)基类 Reference-counting可用于字符串以外的场合,任何class如果其不同的对象可能拥有相同的值,都适用此技术.但是如果重写cl ...
- Netty的深入浅出--79.Netty官方Reference Counted Objects文档说明
netty4之后通过确定的一个the life cycle of certain object 来管理reference counts,一但不在被使用的时候返回一个object pool 简单来说就是 ...
- 深入理解JVM03--判断对象是否存活(引用计数算法、可达性分析算法,最终判定),Eclipse设置GC日志输出,引用
本文是基于周志明的<深入理解Java虚拟机> 堆中几乎存放着Java世界中所有的对象实例,垃圾收集器在对堆回收之前,第一件事情就是要确定这些对象哪些还"存活"着,哪些对 ...
最新文章
- 【C】——线程引入 pthread_self 和 pthread_equal 原因 ——解决不同平台的问题!
- dedecms 如何调用多个不同栏目的文章_SEO排名,同一篇文章,在多个栏目,如何处理?...
- php查询mysql表里的数据_php查询mysql数据表记录实现代码
- boost::filesystem模块演示错误报告的工作原理的测试程序
- 什么是缓存?为什么要使用Redis?
- C++常用的容器(vector、set、list、map)
- SOA架构设计经验分享—架构、职责、数据一致性
- easyui datagrid oncheck 修改行样式_100 种 PPT 图表样式送给你
- 最全iOS数据存储方法介绍:FMDB,SQLite3 ,Core Data,Plist,Preference偏好设置,NSKeyedArchiver归档...
- C语言的全局变量和局部变量的作用域
- ApacheCN 活动汇总 2019.7.12
- TensorFlow实践四步法
- 项目不同阶段的风险特征
- 第 45 届国际大学生程序设计竞赛(ICPC)亚洲区域赛(昆明)(热身赛) C-Statues 题解【dp】【动态规划】
- ADV-234-字符串跳步
- Android kernel中wakeup_sources解析
- python之selenium和xpath简单知晓国服魔兽世界正式服人口普查
- RTX 4070TI和RTX 4090性能差距 RTX4070ti和4070区别对比
- 找技术公司开发小程序需要注意些什么?
- blur事件与click事件冲突的解决办法