jvm调优四:netty堆外内存泄露

Posted 二周目Solo

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了jvm调优四:netty堆外内存泄露相关的知识,希望对你有一定的参考价值。

底层通讯使用netty4.1版本,然而在使用的时候出现了oom异常,这里记录一次堆外内存泄露排查过程,方便后续定位。


一:知识点

堆外内存,就是非堆内内存空间,java可以通过Unsafe类的native方法进行操作,由于不受制于jvm管理,所以堆外内存对象生命周期结束时,需要手动释放。java还是借助于gc释放。



通过上图可以看到,即使堆外一个大的内存对象,在堆内表示可能都非常小,这就是冰山对象,在堆内占用内存非常小。如果触发了几次ygc后,对象bytebuf进入到老年代,由于老年代fullgc次数比较少,那么可能就无法触发回收操作。


在java中存在四种引用类型:

  • 强引用

  • 软引用

  • 弱引用

  • 虚引用:上面三种不做分析,感兴趣的可以查阅相关文档;在java中,directByteBuffer主要是通过虚引用判断堆内对象是否可达。jvm中会有一个单独的线程处理不可达对象,不可达对象添加到引用队列之前,会判断该对象是否为Cleanner。

接上文,堆外内存基于gc机制的回收。本身因为DirectByteBuffer对象很小,只要不触发fullgc,那么一大块的内存一直存在。这个时候,只能依靠jvm的System.gc()调用了。它会中断整个进程,并且当前线程停顿100ms,如果在100ms时间内没有完成,就会抛出oom异常。所以,不能在vm参数中设置:-DisableExplicitGC。


 :netty内存泄露检测


netty所谓的内存泄露,主要是针对池化的ByteBuf。ByteBuf对象在gc掉之前,没有调用release把底下的DirectByteBuffer或byte[]归还到池里。那netty本身也提供了检测机制:

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()

所在当出现leak字样时,需要开启:

-Dio.netty.leakDetectionLevel=paranoid  进行检测


三:排查过程


1:问题

netty服务端在运行了一段时间后,报错日志如下:

jvm调优四:netty堆外内存泄露

错误代码段如下,看上去是超出DIRECT_MEMORY_LIMIT(netty直接内存)上限了:

jvm调优四:netty堆外内存泄露


2:初步定位

使用前文说的定位方法,(本示例使用jmap+jvvm)输出如下:

jvm调优四:netty堆外内存泄露

jvm调优四:netty堆外内存泄露

jvm调优四:netty堆外内存泄露


发现是PoolSubpage(PoolSubpage是netty内存管理中的最小单位)可能存在内存泄露leak,jvm的heap内存只用了几百M,但是物理内存却达到几个G,这更加验证了直接内存(heap堆外内存)泄露。于是重新观察服务端输出日志发现了下面这段leak字样:


jvm调优四:netty堆外内存泄露

见上文我们加入:-Dio.netty.leakDetectionLevel=paranoid重新跑一次server,一段时间后打印如下:

jvm调优四:netty堆外内存泄露


3:代码诊断

找到RouterServerHandler,发现主要处理逻辑都在channelRead方法里:


jvm调优四:netty堆外内存泄露


印象中,netty框架分配的ByteBuf框架会自动释放,业务线程不需要手动释放。但是上面的示例明显是有问题的,于是定位netty源码,发现:基于内存池请求的ByteBuf(主要是PooledDirectByteBuf和PooledHeapByteBuf),在netty 的nioEventLoop线程处理过程中,需要在业务SimpleChannelInboundHandler处理完请求后释放,实现它的抽象方法channelRead()



于是对netty自定义handler重新改造:

运行半小时发现系统无异常,问题解决。

以上是关于jvm调优四:netty堆外内存泄露的主要内容,如果未能解决你的问题,请参考以下文章

netty 堆外内存泄露排查盛宴

记一次Netty堆外内存泄露排查过程

超溜!Netty 堆外内存泄露排查与总结

netty-socketio堆外内存泄露排查盛宴

nettyNetty堆外内存泄露排查盛宴

用Netty发生堆外内存泄露,看老司机一顿排查