目录
一张图片简单明了说说清楚 netty 引用计数!
原文地址:http://netty.io/wiki/reference-counted-objects.html
从 Netty4 开始,某些对象的生命周期由其引用计数来管理,一旦不再使用,Netty 就可以将它们(或其共享资源)返回给对象池(或对象分配器)。垃圾收集和引用队列并没有提供不可达的高效实时保证,Netty 提供了一种可替代的机制----引用计数,代价是略有不便。ByteBuf 是最值得注意的一种,它利用了引用计数来提高分配和回收的性能,本文将解释如何进行引用计数, 何时对引用对象进行销毁, 如何解决缓存泄漏 Leak 等问题?
一、直接上图
二 、 概要说明
2.1 引用计数的基础
- 新建引用计数对象的引用计数为 1
- 释放引用计数对象时,它的引用计数减少 1,如果引用计数达到 0,则引用计数对象将被重新分配或者将其返回它来自的对象池
- 引用计数也可以通过 retain() 操作来增加
- 尝试访问引用计数为 0 的引用计数对象将触发 IllegalReferenceCountException
2.2 谁负责销毁?
一般的经验规则是,最后访问引用计数的对象负责对引用计数对象的销毁。
- 如果【发送】组件将引用计数对象传递给另一个【接收】组件,则发送组件通常不需要销毁它,而是将其推迟到【接收】组件处理。
- 如果一个组件使用了引用计数对象,并且知道其他任何内容都无法访问这个对象(不传递给其他组件),则此组件应该负责销毁它。
2.3 引用对象的派生
2.3.1 派生方法会创建派生缓冲区
- 派生的缓冲区不具有自己的引用计数,共享父缓冲区的引用计数。
- 父缓冲区在创建派生缓冲区时,引用计数不会增加。因此,如果要将派生缓冲区传递给应用程序的其他组件时,则必须先调用 retain 方法。
ByteBuf 的派生方法包括:
- ByteBuf.duplicate()
- ByteBuf.slice()
- ByteBuf.order(ByteOrder)
2.3.2 派生方法会创建派生缓冲区
不是派生的缓冲区,所分配的 ByteBuf 需要被释放。
ByteBuf 的非派生方法:
- ByteBuf.copy()
- ByteBuf.readBytes(int)
2.4 缓冲区泄漏问题解决
如果发生泄漏,你可以找到如下日志信息:
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 -Dio.netty.leakDetection.level 来制定泄漏检测级别。
java -Dio.netty.leakDetectionLevel=advanced …
2.4.1 泄漏检测级别
目前有 4 种泄漏检测:
级别 | 说明 |
---|---|
DISABLED | 禁用泄漏检测。不推荐 |
SIMPLE | 告知 1% 的缓冲区是否存在泄漏。默认情况 |
ADVANCED | 告知1%的缓冲区泄漏的位置。 |
PARANOID | 与 ADVANCED 类似,但是检查所有的缓冲区,此选项在自动测试的阶段很有用。如果构建输出包含了 'LEAK: ',构建失败。 |
2.4.2 避免泄漏的最佳实践
- 在 PARANOID 和 SIMPLE 泄漏检测级别运行你的单元测试和集成测试。
- 使用 SIMPLE 级别运行足够长的时间检测是否发生泄漏
- 如果有泄漏,再使用 ADVANCED 级别获取关于泄漏的提示
- 不要部署存在泄漏的程序到集群
2.4.3 在单元测试中修复泄漏
在单元测试中,很容易忘记释放缓冲区或者消息。它将生成泄漏警告,这并不意味着你的程序有泄漏。你可以使用ReferenceCountUtil.releaseLater() 方法,而不是使用 try-catch 块来包装单元测试以释放所有的缓冲区。
ByteBuf buf = releaseLater(Unpooled.directBuffer(512));
2.5 其他
2.5.1 ByteBufHolder接口
ByteBuf 有时会被包含在一个缓冲区中,例如 DatagramPacket、HttpContent 和 WebSocketframe。这些类型继承了一个名为ByteBufHolder 的公共接口。ByteBufHolder 共享它所包含的缓冲区的引用计数,就像派生的缓冲区一样。
2.5.2 ChannelHandler中的引用计数
- 进站消息
- 当一个事件循环将数据读入 ByteBuf 并触发一个 ChannelRead() 事件时,pipline 中的 ChannelHandle 负责释放 buffer。因此, 处理接收到的数据的 handler 应该在channelRead() 中调用 buffer 的 release() 方法 。
- 如果处理器将缓冲区(或者任何引用计数对象)传递给下一个处理程序,则不需要释放它。
- 如果你有疑问,或者你想要简化消息的释放,可以使用ReferenceCountUtil.release()。
- 出站消息
- 出站消息是由应用程序创建的,将这些消息写入到 pipline 后释放是 Netty 的责任。
- 确保正确释放任何中间对象。(比如编码器中使用的 ByteBuf 等)