当前位置:首页 > Web开发 > 正文

可以使用google-perftools进行监控 堆外内存泄漏诊断 堆外内存泄漏的具体原因比较多

2024-03-31 Web开发

技术图片

上篇文章介绍了Netty内存模型道理,由于Netty在使用不当会导致堆外内存泄漏,网上关于这方面的资料对照少,所以写下这篇文章,专门介绍排查Netty堆外内存相关的常识点,诊断工具,以及排查思路供给参考

现象

堆外内存泄漏的现象主要是,进程占用的内存较高(Linux下可以用top命令检察),但Java堆内存占用并不高(jmap命令检察),常见的使用堆外内存除了Netty,还有基于java.nio下相关接口申请堆外内存,JNI挪用等,下面偏重介绍Netty堆外内存泄漏问题排查

堆外内存释放底层实现 1 java.nio堆外内存释放

Netty堆外内存是基于原生java.nio的DirectByteBuffer东西的根本上实现的,所以有须要先了解下它的释放道理

java.nio供给的DirectByteBuffer供给了sun.misc.Cleaner类的clean()要领,进行系统挪用释放堆外内存,触发clean()要领的情况有2种

(1) 应用措施主动挪用

ByteBuffer buf = ByteBuffer.allocateDirect(1); ((DirectBuffer) byteBuffer).cleaner().clean();

(2) 基于GC回收

Cleaner类担任了java.lang.ref.Reference,GC线程会通过设置Reference的内部变量(pending变量为链表头部节点,discovered变量为下一个链表节点),将可被回收的不成达的Reference东西以链表的方法组织起来

Reference的内部守护线程从链表的头部(head)消费数据,如果消费到的Reference东西同时也是Cleaner类型,线程会挪用clean()要领(Reference#tryHandlePending())

2 Netty noClaner计谋

介绍noClaner计谋之前,需要先理解带有Cleaner东西的DirectByteBuffer在初始化时做了哪些工作:

只有在DirectByteBuffer(int cap)结构要领中才会初始化Cleaner东西,要领中查抄当前内存是否赶过允许的最大堆外内存(可由-XX:MaxDirectMemorySize配置)

如果超过,则会先测验考试将不成达的Reference东西插手Reference链表中,依赖Reference的内部守护线程触发可以被回收DirectByteBuffer关联的Cleaner的run()要领

如果内存还是不敷, 则执行 System.gc(),触发full gc,来回收堆内存中的DirectByteBuffer东西来触发堆外内存回收,如果还是赶过限制,则抛出java.lang.OutOfMemoryError(代码位于java.nio.Bits#reserveMemory()要领)

而Netty在4.1引入可以noCleaner计谋:创建不带Cleaner的DirectByteBuffer东西,这样做的好处是绕开带Cleaner的DirectByteBuffer执行结构要领和执行Cleaner的clean()要领中一些特别开销,当堆外内存不够的时候,不会触发System.gc(),提高性能

hasCleaner的DirectByteBuffer和noCleaner的DirectByteBuffer主要区别如下:

结构器方法差别:
noCleaner东西:由反射挪用 private DirectByteBuffer(long addr, int cap)创建
hasCleaner东西:由 new DirectByteBuffer(int cap)创建

释放内存的方法差别
noCleaner东西:使用 UnSafe.freeMemory(address);
hasCleaner东西:使用 DirectByteBuffer 的 Cleaner 的 clean() 要领

note:Unsafe是位于sun.misc包下的一个类,可以供给内存操纵、东西操纵、线程调理等当处所法,这些要领在提升Java运行效率、增强Java语言底层资源操纵能力方面起到了很大的感化,但不正确使用Unsafe类会使得措施堕落的概率变大,措施不再“安适”,因此官方不保举使用,并可能在未来的jdk版本移除

Netty在启动时需要判断查抄当前环境、环境配置参数是否允许noCleaner计谋(具体逻辑位于PlatformDependent的static代码块),例如运行在Android下时,是没有Unsafe类的,不允许使用noCleaner计谋,如果不允许,则使用hasCleaner计谋

note:可以挪用PlatformDependent.useDirectBufferNoCleaner()要领检察当前Netty措施是否使用noClaner计谋

ByteBuf.release()触发机制

业界有一种误解认为 Netty 框架分配的 ByteBuf,框架会自动释放,业务不需要释放;业务创建的 ByteBuf 则需要本身释放,Netty 框架不会释放

孕育产生这种误解是有原因的,Netty框架是会在一些场景挪用ByteBuf.release()要领:

1 入站动静措置惩罚惩罚

当措置惩罚惩罚入站动静时,Netty会创建ByteBuf读取channel上的动静,并触发挪用pipeline上的ChannelHandler措置惩罚惩罚,应用措施界说的使用ByteBuf的ChannelHandler需要卖力release()

public void channelRead(ChannelHandlerContext ctx, Object msg) { ByteBuf buf = (ByteBuf) msg; try { ... } finally { buf.release(); } }

如果该ByteBuf不由当前ChannelHandler措置惩罚惩罚,则通报给pipeline上下一个handler:

public void channelRead(ChannelHandlerContext ctx, Object msg) { ByteBuf buf = (ByteBuf) msg; ... ctx.fireChannelRead(buf); }

常用的我们会通过担任ChannelInboundHandlerAdapter界说入站动静措置惩罚惩罚的handler,这种情况下如果所有措施的hanler都没有挪用release()要领,该入站动静Netty最后并不会release(),会导致内存泄漏

温馨提示: 本文由Jm博客推荐,转载请保留链接: https://www.jmwww.net/file/web/32405.html