有过痛苦的经历,特别能写出深入的文章 —— 凯尔文. 肖html
直接内存是IO框架的绝配,但直接内存的分配销毁不易,因此使用内存池能大幅提升性能,也告别了频繁的GC。但,要从新培养被Java的自动垃圾回收惯坏了的惰性。app
Netty有一篇必读的文档 官方文档翻译:引用计数对象 ,在此基础上补充一些本身的理解和细节。框架
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,状况更为复杂。测试
在C时代,咱们喜欢让malloc和free成对出现,而在Netty里,由于Handler链的存在,ByteBuf常常要传递到下一个Hanlder去而不复还,因此规则变成了谁是最后使用者,谁负责释放。spa
另外,更要注意的是各类异常状况,ByteBuf没有成功传递到下一个Hanlder,还在本身地界里的话,必定要进行释放。.net
在AbstractNioByteChannel.NioByteUnsafe.read() 处建立了ByteBuf并调用 pipeline.fireChannelRead(byteBuf) 送入Handler链。翻译
根据上面的谁最后谁负责原则,每一个Handler对消息可能有三种处理方式日志
假设每个Handler都把消息往下传,Handler并也不知道谁是启动Netty时所设定的Handler链的最后一员,因此Netty在Handler链的最末补了一个TailHandler,若是此时消息仍然是ReferenceCounted类型就会被release掉。
netty
要发送的消息由应用所建立,并调用 ctx.writeAndFlush(msg) 进入Handler链。在每一个Handler中的处理相似InBound Message,最后消息会来到HeadHandler,再通过一轮复杂的调用,在flush完成后终将被release掉。
多层的异常处理机制,有些异常处理的地方不必定准确知道ByteBuf以前释放了没有,能够在释放前加上引用计数大于0的判断避免释放失败;
有时候不清楚ByteBuf被引用了多少次,但又必须在此进行完全的释放,能够循环调用reelase()直到返回true。
所谓内存泄漏,主要是针对池化的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被建立和访问的地方。
每当各类ByteBufAllocator 建立ByteBuf时,都会问问是否须要采样,Simple和Advanced级别下,就是以113这个素数来取模(害我看文档的时候还在瞎担忧,1%,万一泄漏的地方有所规律,恰好躲过了100这个数字呢,好比都是3倍数的),命中了就建立一个Java堆外内存扫盲贴里说的PhantomReference。而后建立一个Wrapper,包住ByteBuf和Reference。
simple级别下,wrapper只在执行release()时调用Reference.clear(),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://calvin1978.blogcn.com/articles/netty-leak.html