(13)堆外内存 OOM

2020-12-14  本文已影响0人  hedgehog1112

内存使用率不断上升,开始用 SWAP 内存,同时 GC 时间飙升,线程 Block ,top 命令发现 Java 进程RES 超过-Xmx 大小。确定堆外内存泄漏

ps:RES:resident memory usage 常驻内存:

    1、进程当前用的内存大小,包含其他进程共享,不包括swap out

    2、如申请100m的内存,实际用10m,只增长10m,与VIRT相反

    3、统计加载的库文件所占内存大小

一、主要两种原因:

1)通过 UnSafe#allocateMemory,ByteBuffer#allocateDirect 主动申请堆外内存没释放,常见于 NIO、Netty 等相关组件。

2)代码通过 JNI 调用 Native Code 申请内存没释放

二、策略

哪种原因造成的堆外内存泄漏?

    1)用 NMT(NativeMemoryTracking) 分析。在项目中添加 -XX:NativeMemoryTracking=detail JVM参数重启项目(NMT 会带来 5%~10% 的性能损耗)。

    2)用命令 jcmd pid VM.native_memory detail 查看内存分布。重点观察 total 中的 committed,因为 jcmd 命令显示的内存包含堆内内存、Code 区域、通过 Unsafe.allocateMemory 和 DirectByteBuffer 申请的内存,但不包含其他 Native Code(C 代码)申请的堆外内存。

total 中 committed 和 top 中的 RES 相差不大,为主动申请未释放,相差较大,JNI 调用造成的

原因一:主动申请未释放

1、控制申请最大值 -XX:MaxDirectMemorySize=size 默认和 -Xmx 相等

2、NIO 和 Netty 都取 -XX:MaxDirectMemorySize 值限制大小。NIO 和 Netty 有计数器字段,计算已申请堆外内存大小,监控堆外内存使用情况,超过最大值限制,抛OOM

    ps:NIO 中是 java.nio.Bits#totalCapacity、Netty中 io.netty.util.internal.PlatformDependent#DIRECT_MEMORY_COUNTER。

    NIO 中是:OutOfMemoryError: Direct buffer memory。

    Netty 中是:OutOfDirectMemoryError: failed to allocate capacity byte(s) of direct memory (used: usedMemory , max: DIRECT_MEMORY_LIMIT )。

3、解决:Debug确定是否释放内存。另外检查是否有System.gc

原因二:JNI 调 Native Code 申请的内存未释放

排查困难, Google perftools + Btrace分析问题代码在哪

gperftools 是 Google 开发的一款非常实用的工具集,它的原理是在 Java 应用程序运行时,当调用 malloc 时换用它的 libtcmalloc.so,这样就能对内存分配情况做一些统计。我们使用 gperftools 来追踪分配内存的命令。如下图所示,通过 gperftools 发现 Java_java_util_zip_Inflater_init 比较可疑。

接下来可以使用 Btrace,尝试定位具体的调用栈。Btrace 是 Sun 推出的一款 Java 追踪、监控工具,可以在不停机的情况下对线上的 Java 程序进行监控。如下图所示,通过 Btrace 定位出项目中的 ZipHelper 在频繁调用 GZIPInputStream ,在堆外内存分配对象。

最终定位到是,项目中对 GIPInputStream 的使用错误,没有正确的 close()。

除了项目本身的原因,还可能有外部依赖导致的泄漏,如 Netty 和 Spring Boot,详细情况可以学习下这两篇文章:《疑案追踪:Spring Boot内存泄露排查记》、《Netty堆外内存泄露排查盛宴》。

总结

用 NMT + jcmd 分析泄漏的堆外内存是哪里申请,确定原因后,用不同手段,进行原因定位

https://mp.weixin.qq.com/s/RFwXYdzeRkTG5uaebVoLQw

上一篇 下一篇

猜你喜欢

热点阅读