JAVA后端一次生产上堆外内存泄漏排查

1,908 阅读3分钟

概序

目前项目工程(JAVA)和数据算法库(C++)通过JNA接口实现算法库的C++程序分配内存给JAVA使用的。但按照JAVA设计的机制,只会自动回收JAVA自己分配的内存,不会回收C++分配的内存,所以在C++分配的内存反馈给JAVA程序使用后,如果JAVA程序不执行回收内存操作,就会造成内存泄漏

内存泄漏表现现象

1.生产上内存泄漏告警

1726801042232.png

通过top -c查看内存使用情况

image.png

程序堆内存设置是4G 居然增长的42.4G,可以肯定的是堆外内存的泄漏,如果是java堆内内存泄漏程序早就挂了

1726801451687.png

堆外内存溢出问题分析

堆外内存溢出分为两种:

  一、是受JAVA控制的 底层使用Unsafe申请过 例如ByteBuffer.allocateDirect()方法申请的堆外内存
  二、是其他语言与JAVA语言交互的时候申请过内存,不受java管理。例如C++、C 等语言申请的内存 

堆外内存排查过程

首先项目程序配置-XX:NativeMemoryTracking=detail 参数并启动测试环境,隔天观察程序占用内存使用状况。

top -c 命令查看内存占用情况

image.png

然后使jcmd pid VM.native_memory 命令查看堆外内存,JAVA堆内以及堆外所有的内存总共加起来才5.4G 那还有5.7G去哪了,排除JAVA堆外内存泄漏的风险。

image.png 该项目程序和数据算法库(C++)交互是通过JNA接口实现算法库的C++程序分配内存给JAVA使用的,故可以肯定是本地的C++调用造成的内存泄漏,故排除java的堆外内存的内存溢出情况。

我们使用pmap -x pid 查看内存块情况,可以明显看到许多匿名的占用内存块情况

image.png

  1. 用 /proc/1785237/smaps查看内存中起始地址,找到可疑内存块起始地址和结束地址

image.png 2. 使用gdb -p pid  进入gdb命令行, 然后dump文件命令如下

    dump memory /aiops/twin_data/jxq.bin 0x7feb38457000 0x7feb3c000000

3 使用strings  jxq.bin >  jxq.txt  将文件字符串放到/tmp/jxq.txt 文件,查看内存块中显示字符,发现这些字符是调用C++接口返回结果集。 image.png

4.根据内存块中提示字符查找相关的代码,经过查明调用C++算法接口返回的是char* 指针类型,JAVA代码中是用string串类型接受导致内存泄漏。

image.png 5. 修改方案  接口中的方法统一返回由string类型改为pointer指针类型,接口增加释放指针的方法 freeStringBuffer();

image.png 代码中调用增加try{}catch{}finally{}在finally代码块中调用freeStringBuffer释放内指针

image.png

验证结果

上线后该服务器内存曲线变化 image.png 使用top -c 多次观察这两天的内存变化状况 发现内存已经变的比较平稳

第一天 image.png

第二天 image.png 通过曲线变化,以及top命令来长时间跟踪内存的情况,判断已经修复