起因:
最近新上线的服务内存(部署在linux平台)每天一天都会上涨一点点,现象如下图所示。想到每天要通过一个SO库与一个数据源进行TCP的连接与断开,于是便展开排查工作。
因为不是紧急的线上问题,于是在开发站搭建环境进行问题重现。主站上面程序是每天9点进行连接操作,下午四点进行TCP长连接的断开操作,开发站为了放大这个问题,写了一个循环进行重复连接与断开,观察内存变化情况。
排查步骤:
step1:确认是否内存是在java虚拟机内部产生泄漏。
程序启动时加如下参数ativeMemoryTracking观察内存(注意要在-jar前面,不然不会起作用)
/usr/local/jdk1.8.0_251/bin/java -XX:NativeMemoryTracking=detail -jar
使用如下命令观察虚拟机内部内存情况
jcmd <pid> VM.native_memory
发现java虚拟机申请的内存远小于该进程实际占用的内存,所以可以确定是由于C++申请内存没有释放导致的泄漏问题。
step2:操作系统层面确定是哪块内存泄漏
使用pmap命令比较查看该进程哪一块内存只上涨不回收,发现上涨的内存都是堆内存,下面的工作就是进一步分析堆内存是哪些线程进行申请并且没有进行释放。
pmap -x <pid>| sort -k 3 -n -r
step3:使用gperftools进行代码泄漏分析,该工具的介绍、安装和使用可以参考文章最后的参考文献。
首先在启动脚本添加相关分析SO库的配置
export LD_PRELOAD=/usr/local/lib/libtcmalloc.so
export HEAPPROFILE=/perftools #分析文件输出位置
然后重启程序并启动疑似内存泄漏程序,使用命pprof令分析输出结果。
发现主要内存泄漏是由于HandleSnapshotData和handleCodeTableData方法导致的,至此排查结束,联系该so库的开发团队进行问题反馈。
pprof --text libtapi_jni.so /perftools/.0014.heap | head -10
pprof --text libtapi_jni.so /perftools/.0057.heap | head -10
总结:
- 虽然这个内存泄漏问题每天只会涨几十M,但是这始终是一个隐藏的雷点,说不定哪天在线上就爆雷了,这需要开发人员像呵护孩子一样呵护自己的服务,观察他的喜怒哀乐,时长给予他关怀。
- 在排查问题的过程当中遇到了很多坑,比如jvm参数添加后程序启动就崩溃,后来查到是由于该jdk版本的一个bug,升级到1.8.0_251就能解决;又比如使用gperftools这个工具时,因为输出的文件太多,没有细致的去观察输出的文件规律,导致浪费了几个小时去查问题等等,需要在以后的工作中更加细致认真。
参考文献:
https://tech.meituan.com/2019/01/03/spring-boot-native-memory-leak.html
https://github.com/gperftools/gperftools
https://www.jianshu.com/p/8b996698e2e3