文档章节

Netty之有效规避内存泄漏

Mr_Young
 Mr_Young
发布于 2015/08/24 14:45
字数 1854
阅读 1193
收藏 7

有过痛苦的经历,特别能写出深刻的文章 —— 凯尔文. 肖

直接内存是IO框架的绝配,但直接内存的分配销毁不易,所以使用内存池能大幅提高性能。但,要重新培养被Java的自动垃圾回收惯坏了的惰性。

Netty有一篇必读的文档 官方文档翻译:引用计数对象 ,在此基础上补充一些自己的理解和细节。

1.为什么要有引用计数器

Netty里四种主力的ByteBuf,
其中UnpooledHeapByteBuf 底下的byte[]能够依赖JVM GC自然回收;而UnpooledDirectByteBuf底下是DirectByteBuffer,如Java堆外内存扫盲贴所述,除了等JVM GC,最好也能主动进行回收;而PooledHeapByteBuf 和 PooledDirectByteBuf,则必须要主动将用完的byte[]/ByteBuffer放回池里,否则内存就要爆掉。所以,Netty ByteBuf需要在JVM的GC机制之外,有自己的引用计数器和回收过程。

一下又回到了C的冰冷时代,自己malloc对象要自己free。 但和C时代又不完全一样,内有引用计数器,外有JVM的GC,情况更为复杂。

2. 引用计数器常识

  • 计数器基于 AtomicIntegerFieldUpdater,为什么不直接用AtomicInteger?因为ByteBuf对象很多,如果都把int包一层 AtomicInteger花销较大,而AtomicIntegerFieldUpdater只需要一个全局的静态变量。

  • 所有ByteBuf的引用计数器初始值为1。

  • 调用release(),将计数器减1,等于零时, deallocate()被调用,各种回收。

  • 调用retain(),将计数器加1,即使ByteBuf在别的地方被人release()了,在本Class没喊cut之前,不要把它释放掉。

  • 由duplicate(), slice()和order(ByteOrder)所创建的ByteBuf,与原对象共享底下的buffer,也共享引用计数器,所以它们经常需要调用retain()来显示自己的存在。

  • 当引用计数器为0,底下的buffer已被回收,即使ByteBuf对象还在,对它的各种访问操作都会抛出异常。


3.谁来负责Release

在C时代,我们喜欢让malloc和free成对出现,而在Netty里,因为Handler链的存在,ByteBuf经常要传递到下一个Hanlder去而不复还,所以规则变成了谁是最后使用者,谁负责释放。

另外,更要注意的是各种异常情况,ByteBuf没有成功传递到下一个Hanlder,还在自己地界里的话,一定要进行释放。

3.1 InBound Message
在AbstractNioByteChannel.NioByteUnsafe.read() 处,配置好的ByteBufAllocator创建相应ByteBuf并调用 pipeline.fireChannelRead(byteBuf) 送入Handler链。

根据上面的谁最后谁负责原则,每一个Handler对消息可能有三种处理方式

对原消息不做处理,调用 ctx.fireChannelRead(msg)把原消息往下传,那不用做什么释放。
将原消息转化为新的消息并调用 ctx.fireChannelRead(newMsg)往下传,那必须把原消息release掉。
如果已经不再调用ctx.fireChannelRead(msg)传递任何消息,那更要把原消息release掉。
假设每一个Handler都把消息往下传,Handler并也不知道谁是启动Netty时所设定的Handler链的最后一员,所以Netty会 在Handler链的最末补一个TailHandler,如果此时消息仍然是ReferenceCounted类型就会被release掉。
不过如果我们的业务Hanlder不再把消息往下传了,这个TailHandler就派不上用场。
3.2 OutBound Message
要发送的消息通常由应用所创建,并调用 ctx.writeAndFlush(msg) 进入Handler链。在每一个Handler中的处理类似InBound Message,最后消息会来到HeadHandler,再经过一轮复杂的调用,在flush完成后终将被release掉。

3.3 异常发生时的释放
多层的异常处理机制,有些异常处理的地方不一定准确知道ByteBuf之前释放了没有,可以在释放前加上引用计数大于0的判断避免异常;

有时候不清楚ByteBuf被引用了多少次,但又必须在此进行彻底的释放,可以循环调用reelase()直到返回true。

4. 内存泄漏检测

所谓内存泄漏,主要是针对池化的ByteBuf。ByteBuf对象被JVM GC掉之前,没有调用release()去把底下的DirectByteBuffer或byte[]归还到池里,会导致池越来越大。而非池化的 ByteBuf,即使像DirectByteBuf那样可能会用到System.gc(),但终归会被release掉的,不会出大事。

Netty担心大家一定会不小心就搞出个大新闻来,因此提供了内存泄漏的监测机制。

Netty默认就会从分配的ByteBuf里抽样出大约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()


这句话报告有泄漏的发生,提示你用-D参数,把防漏等级从默认的simple升到advanced,具体看到被泄漏的ByteBuf创建的地方和被访问的地方。

  • 禁用(DISABLED) - 完全禁止泄露检测,省点消耗。

  • 简单(SIMPLE) - 默认等级,告诉我们取样的1%的ByteBuf是否发生了泄露,但总共一次只打印一次,看不到就没有了。

  • 高级(ADVANCED) - 告诉我们取样的1%的ByteBuf发生泄露的地方。每种类型的泄漏(创建的地方与访问路径一致)只打印一次。

  • 偏执(PARANOID) - 跟高级选项类似,但此选项检测所有ByteBuf,而不仅仅是取样的那1%。在高压力测试时,对性能有明显影响。


实现细节
每当各种ByteBufAllocator 创建ByteBuf时,都会问问是否需要采样,Simple和Advanced级别下,就是以113这个素数来取模(害我看文档的时候还在瞎担心,1%, 万一泄漏的地方有所规律,刚好躲过了100这个数字呢,比如都是3倍数的),命中了就创建一个Java堆外内存扫盲贴里说的 PhantomReference。然后创建一个Wrapper,包住ByteBuf和Reference。

Simple级别下,wrapper只在执行release()时调用Reference.clear()把Reference清理掉,Advanced级别下则会记录每一个创建和访问的动作。

当GC发生,还没有被clear()的Reference就会被JVM放入到之前设定的ReferenceQueue里。

在每次创建PhantomReference时,都会顺便看看有没有因为忘记执行release()把Reference给clear掉,在GC 时被放进了ReferenceQueue的对象,有则以 "io.netty.util.ResourceLeakDetector”为logger name,写出前面例子里的Error级别的日日志。顺便说一句,Netty能自动匹配日志框架,先找Slf4j,再找Log4j,最后找JDK logger。

值得说三遍的事
一定要盯紧log里有没有出现 "LEAK: "字样,因为Simple级别下它只会出现一次,所以不要依赖自己的眼睛,要依赖grep。如果出现了,而且你用的是PooledBuf,那一定是问题, 不要有任何的侥幸,立刻用"-Dio.netty.leakDetectionLevel=advanced" 再跑一次,看清楚它创建和最后访问的地方。

功能测试时,最好开着"-Dio.netty.leakDetectionLevel=paranoid"

但是,怎么测试都可能有没覆盖到的分支,如果内存尚够,可以适当把-XX:MaxDirectMemorySize 调大,反正只是max,平时也不会真用了你的。然后监控其使用量,及时报警。

本文转自:花钱的年华

本文转载自:http://www.iteye.com/news/30866

共有 人打赏支持
Mr_Young
粉丝 3
博文 53
码字总数 8989
作品 0
朝阳
程序员
Netty系列之Netty高可靠性原理分析

背景 1.1. 宕机的代价 1.1.1. 电信行业 毕马威国际(KPMG International)在对46个国家的74家运营商进行调查后发现,全球通信行业每年的收益流失约为400亿美元,占总收入的1%-3%。导致收益流失...

tantexian
2016/07/22
69
0
感悟优化——Netty对JDK缓冲区的内存池零拷贝改造

NIO中缓冲区是数据传输的基础,JDK通过ByteBuffer实现,Netty框架中并未采用JDK原生的ByteBuffer,而是构造了ByteBuf。 ByteBuf对ByteBuffer做了大量的优化,比如说内存池,零拷贝,引用计数(...

Janti
07/12
0
0
Netty in Action ——— ChannelHandler 和 ChannelPipeline

本文是Netty文集中“Netty in action”系列的文章。主要是对Norman Maurer and Marvin Allen Wolfthal 的 《Netty in action》一书简要翻译,同时对重要点加上一些自己补充和扩展。 概要 Ch...

tomas家的小拨浪鼓
2017/10/30
0
0
第十六章:从EventLoop取消注册和重新注册

本章介绍 EventLoop 从EventLoop注册和取消注册 在Netty中使用旧的Socket和Channel Netty提供了一个简单的方法来连接Socket/Channel,这是在Netty之外创建并转移他们的责任到Netty。这允许你...

李矮矮
2016/09/27
17
0
Netty5.x中新增和值得注意的点

支持Android 提供了: 移动设备变成更加强大 通过Ice Cream Sandwich解决了在ADK中最著名的与NIO和SSLEngine相关的问题,且 用户显然想要重用他们应用中的的编解码和处理器代码。 我们决定官...

天下杰论
2014/08/08
0
0

没有更多内容

加载失败,请刷新页面

加载更多

解决访问swaggerUI接口文档显示basic-error-controler问题

使用swagger生成接口文档后,访问http://localhost:8888/swagger-ui.html#/,显示如下: 解决方法: public Docket createRestApi() {return new Docket(DocumentationType.SWAGGER_2)......

张欢19933
27分钟前
1
0
区块链教程以太坊源码分析core-state-process源码分析(二)

兄弟连区块链教程以太坊源码分析core-state-process源码分析(二):关于g0的计算,在黄皮书上由详细的介绍和黄皮书有一定出入的部分在于if contractCreation && homestead {igas.SetUin...

兄弟连区块链入门教程
32分钟前
0
0
BLAKE2 — fast secure hashing

BLAKE2 — fast secure hashing SPECS | CODE | B2SUM | CONTACT | USERS | THIRD-PARTY SOFTWARE | CRYPTANALYSIS | FAQ Come from http://www.blake2.net/ BLAKE2 is a cryptographic has......

openthings
38分钟前
4
0
Titan Framework MongoDB深入理解3

在前两篇文章中,我们介绍了操作Mongo数据库的类型Curd和Finder,下面要理解的是框架内mongoDB操作的条件类型——MongoDBQueryCondition。 MongoDBQueryCondition是一个接口,规定了一些实现...

云季科技
39分钟前
0
0
数据结构(算法)-树

#include <iostream>#include <malloc.h>using namespace std;#define MaxSize 100typedef char ElemType;typedef struct node{ElemType data;struct node *left ,*......

ashuo
41分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部