Netty之有效规避内存泄漏

标签: netty 有效 内存泄漏 | 发表时间:2015-08-26 09:42 | 作者:
出处:http://m635674608.iteye.com

http://www.iteye.com/news/30866

 

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

直接内存是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,平时也不会真用了你的。然后监控其使用量,及时报警。 



已有 0 人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



相关 [netty 有效 内存泄漏] 推荐:

Netty之有效规避内存泄漏

- - zzm
有过痛苦的经历,特别能写出深刻的文章 —— 凯尔文. 直接内存是IO框架的绝配,但直接内存的分配销毁不易,所以使用内存池能大幅提高性能. 但,要重新培养被Java的自动垃圾回收惯坏了的惰性. Netty有一篇必读的文档 官方文档翻译: 引用计数对象 ,在此基础上补充一些自己的理解和细节. 1.为什么要有引用计数器 .

内存泄漏

- - CSDN博客系统运维推荐文章
程序申请了堆空间,但是“忘记”释放,导致该块区域在程序结束前无法被再次使用导致的. 泄漏时间长了,就会导致用户空间内存不足,严重的导致死机. 如果泄漏比较严重,很容易察觉;但是有些泄漏很缓慢,不容易察觉,但是软件会运行很长时间后,会慢慢导致严重问题,而且当发现症状的时候,基本上已经是比较晚的时候了,想要识别泄漏,还是可以实现的,本篇文章来聊聊内存操作的原理.

java内存泄漏

- - 编程语言 - ITeye博客
不论哪种语言的内存分配方式,都需要返回所分配内存的真实地址,也就是返回一个指针到内存块的首地址. Java中对象是采用new或者反射的方法创建的,这些对象的创建都是在堆(Heap)中分配的,所有对象的回收都是由Java虚拟机通过垃圾回收机制完成的. GC为了能够正确释放对象,会监控每个对象的运行状况,对他们的申请、引用、被引用、赋值等状况进行监控,Java会使用有向图的方法进行管理内存,实时监控对象是否可以达到,如果不可到达,则就将其回收,这样也可以消除引用循环的问题.

浅谈Java--内存泄漏

- - ITeye博客
      JAVA的垃圾回收机制,让许多程序员觉得内存管理不是很重要,但是内存内存泄露的事情恰恰这样的疏忽而发生,特别是对于Android开发,内存管理更为重要,养成良好的习惯,有利于避免内存的泄漏..     这里可以把许多对象和引用看成是有向图,顶点可以是对象也可以是引用,引用关系就是有向边.

Android 解析内存泄漏

- - CSDN博客移动开发推荐文章
1、引用没释放造成的内存泄露.        1.1、注册没取消造成的内存泄露.        这种 Android的内存泄露比纯 Java的内存泄露还要严重,因为其他一些Android程序可能引用我们的Anroid程序的对象(比如注册机制). 即使我们的Android程序已经结束了,但是别的引用程序仍然还有对我们的Android程序的某个对象的引用,泄露的内存依然不能被垃圾回收.

Perfdog玩转内存泄漏

- - 操作系统 - ITeye博客
最近QC同学在跑游戏的过程中发现玩的时间久了游戏会发生闪退,经过搜集信息后排除了功能性bug的. 拿到真机,USB连接,杀掉多余后台进程,打开Perfdog,接下来一顿操作猛如虎,Perfdog具体操作不在赘述,有关perfdog怎么使用的教程可以参考. 此图一出,基本就可以断定内存泄露了,这是正常玩游戏,游戏运行了30分钟的内存趋势图;.

HTTP/2 in Netty

- -
Here, we created a context for the server with a JDK SSL provider, added a couple of ciphers, and configured the Application-Layer Protocol Negotiation for HTTP/2..

shared_ptr真能防止内存泄漏吗?

- Roger - codedump
这个命题有些诡异,因为shared_ptr设计的初衷就是为了防止内存泄漏,但是先别急,等我把问题描述清楚.. 事出缘由是这几天项目出现一个内存泄漏的bug,之前这部分是使用shared_ptr封装了很多指针的操作,后来出于效率的考虑,改回了裸指针.由于我们使用的google tcmalloc做内存分配,它自带了检测内存泄漏的功能,于是在单元测试的时候就被检查出了内存泄漏..

(转)ThreadLocal的内存泄漏问题

- - 编程语言 - ITeye博客
原文:http://www.godiscoder.com/?p=479. 在最近一个项目中,在项目发布之后,发现系统中有内存泄漏问题. 表象是堆内存随着系统的运行时间缓慢增长,一直没有办法通过gc来回收,最终于导致堆内存耗尽,内存溢出. 开始是怀疑ThreadLocal的问题,因为在项目中,大量使用了线程的ThreadLocal保存线程上下文信息,在正常情况下,在线程开始的时候设置线程变量,在线程结束的时候,需要清除线程上下文信息,如果线程变量没有清除,会导致线程中保存的对象无法释放.

Android内存泄漏检测-LeakCanary

- - CSDN博客推荐文章
添加LeakCanary依赖包. 在主模块app下的build.gradle下添加如下依赖:. 添加Application子类. 首先创建一个ExampleApplication,该类继承于Application,在该类的onCreate方法中添加如下代码开启LeakCanary监控:. 在配置文件中注册ExampleApplication.