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

简介: 堆外内存泄露的排查方法,kafka,ScriptEngine脚本引擎的内存泄露问题

背景:车联网应用,高频的监控数据解析入库查询。涉及到的中间件包括OTS、KAFKA、REDIS、RDS、DUBBO、MaxCompute、DataHub等等。

表现:java堆内存呈现非常规律的锯齿状,YGC,FGC都是正常的表现。但是在top命令中JAVA进程使用的RES持续增长,涉及到车联网产品8个应用。部分应用(8G内存)连续运行2天就使用了将近80%的内存。部分应用运行大概一周后占用了80%内存。截图一个典型的内存占比增长和堆内存的变化曲线
image
image

排查的过程比较曲折,网上也翻阅了不少前人的经验。不过较多的都是一些特定的例子,并没有形成系统的方法。不过通过例子确实帮助找到几个主要的堆外内存泄露的坑。其中有一篇文章的方法思路跟我后来总结的较为接近https://www.liangzl.com/get-article-detail-5958.html 。可以借鉴。
我这里总结下核心的排查思路。
1:JAVA堆内存泄露排查,因为这里主要讲堆外内存泄露的问题。所以这里主要讲下工具方法然后直接贴图了。主要使用Eclipse的MAT(Memory Analyzer Tool)来分析dump文件。工具会给出堆内内存泄露的点,这些都是比较直接的。上图(本例里面使用了ScheduleX,使用了几个大的队列,并不是泄漏点) image
2:堆外内存排查,这里直接把套路列出来,不赘述自己走过的弯路。

  • 使用MAT的Histogram查找java.lang.ref.Finalizer(至于这个类是干啥的,可以自行百度)。如果这里能过滤出来上万的对象,那基本上这里一定存在着堆外内存泄露的点了(比如这里的Socket连接,通过List Objects--with outgoing reference)image
    image

一般你都可以通过reference对象一步步抽丝剥茧找到这个等待Finalizer队列回收的对象的相关属性,我就是通过这个定位到其中一个应用未使用数据库连接池,导致非常多的socket连接等待关闭,而socket连接肯定会使用到系统的读写缓存,自然进程就消耗了较多的堆外内存。

  • 当然通过java.lang.ref.Finalizer我们还可能找到除socket连接之外的一些待释放资源,比如我就找到很多java.util.zip.Deflater(Inflater)对象。看了下他的代码,是重写了finalize的方法的,所以这里是个坑,高频的使用gzip解压缩得时候,就会有这个问题。而梳理我们的应用,我们使用的kafka在消息发送的时候选择了gzip的压缩方式,所以整个应用群在这部分内存泄露上存在着相同问题。将kafka的消息压缩方式改成snappy,解决了整个应用群80%的内存泄露问题。
  • 前面讲到kafka的gzip消息发送配置导致了堆外内存泄露80%的问题,那剩下的20%的问题在哪里?一个是根据对应用的特点,比如我这边剩下的应用特别之处是使用了动态脚本语言,那就从这个点切入进去,这次百度没啥发现,试了google,发现这个问题
    https://bugs.openjdk.java.net/browse/JDK-8197544 把这个当做怀疑点验证发现ScriptEngine.eval 方法存在着内存泄露的问题。因为开发人员使用了直接eval代码段的方式,每次处理的时候都是直接eval脚本。后来改成script function的调用方法,并且保证script的eval只执行一次,后续都是缓存这个engine然后invokeFunction传参。(这有点类似预编译之后调用的意思?)

image
image

  • 上面是因为自己熟悉应用特点,所以针对性地去排查的动态脚本引擎的问题,假设我不知道这个特点,如果定位可能的问题,通过perf record,perf report去查看所有JNI的方法调用,也许这里能知道蛛丝马迹。比如这里的脚本引擎调用。image
  • java.lang.ref.Finalizer较多的时候,也可以考虑调优下JVM的启动参数,我这里调整了下 -XX:-UseAdaptiveSizePolicy 关闭了JVM的动态内存尺寸调整,避免ED,S0,S1的动态调整,担心S0,S1太小会不会导致更多的对象进到老年区。

结论:通过java.lang.ref.Finalizer来查找等待系统回收的资源,这里是内存泄露重灾区。通过perf等工具查找可疑的JNI调用,通过对应用特点分析找到可疑的点。
最后附上一张调整后的对比图
image

相关文章
|
5月前
|
Java 数据库连接
Java中的内存泄漏排查与预防方法
Java中的内存泄漏排查与预防方法
|
7月前
|
缓存 移动开发 关系型数据库
Linux 内存 占用较高问题排查
Linux 内存 占用较高问题排查
138 2
|
7月前
|
缓存 Linux
kswapd0内存过高排查经历
kswapd0内存过高排查经历
447 1
|
5月前
|
监控 Java
Java中的内存泄漏分析与排查技巧
Java中的内存泄漏分析与排查技巧
|
5月前
|
存储 监控 算法
LeakCanary 的内存泄露问题排查
LeakCanary 的内存泄露问题排查
72 0
|
3月前
|
监控 Java Linux
redisson内存泄漏问题排查
【9月更文挑战第22天】在排查 Redisson 内存泄漏问题时,首先需确认内存泄漏的存在,使用专业工具(如 JProfiler)分析内存使用情况,检查对象实例数量及引用关系。其次,检查 Redisson 使用方式,确保正确释放资源、避免长时间持有引用、检查订阅和监听器。此外,还需检查应用程序其他部分是否存在内存泄漏源或循环引用等问题,并考虑更新 Redisson 到最新版本以修复潜在问题。
101 5
|
4月前
|
JavaScript Java 开发工具
Electron V8排查问题之接近堆内存限制的处理如何解决
Electron V8排查问题之接近堆内存限制的处理如何解决
240 1
|
7月前
|
缓存 算法 安全
【JVM故障问题排查心得】「Java技术体系方向」Java虚拟机内存优化之虚拟机参数调优原理介绍(二)
【JVM故障问题排查心得】「Java技术体系方向」Java虚拟机内存优化之虚拟机参数调优原理介绍
67 0
|
7月前
|
缓存 Java C#
【JVM故障问题排查心得】「Java技术体系方向」Java虚拟机内存优化之虚拟机参数调优原理介绍(一)
【JVM故障问题排查心得】「Java技术体系方向」Java虚拟机内存优化之虚拟机参数调优原理介绍
154 0
|
5月前
|
监控 安全 Java
JVM内存问题之排查Direct Memory泄漏有哪些常用方法
JVM内存问题之排查Direct Memory泄漏有哪些常用方法
138 2