内存泄漏排查工具使用及top命令显示问题

问题描述

线上服务压测过后,部分机器的监控显示的rss内存使用率会一直增长,中间不会fullgc,直到内存分配不了,直接down机,进程被kill掉。

当前环境

jdk1.8.0_91

g1垃圾回收器

内存配置

排查过程

1.错误示范

同样配置机器进行压测看能否复现场景,先按照上面的参数启动应用,

使用top命令:

再过两分钟后 使用top命令得到如下:

可以看到res在应用启动后,短短时间内就增长了3个g,同时这时候只是刚启动应用。

我们先来看看java进程的内存分布,显然泄漏主要来自分为JVM的堆区,非堆区。非JVM的区域。这些都有可能。

那么这个时候使用jmap命令,先看堆区。

jmap -heap 84368

可以看到heap区只使用了1181M。

NMT内存追踪文档: 聊聊HotSpot VM的Native Memory Tracking-腾讯云开发者社区-腾讯云

在启动参数里面加了

-XX:NativeMemoryTracking=detail

可以用来跟踪JVM的非堆区和堆区内存分布,使用命令

jcmd 84368 VM.native_memory summary scale=MB

综上,9个多g的res,堆内存只用了1181M,其他Class,Thread,Code,Internal,Symbol等等加在一起也完全达不到9个多g。

显而易见,我天真的认为存在堆外内存泄漏,并且是NMT都追踪不到的非JVM的JNI之类的native code的堆外内存泄漏。

2.尝试多种办法

开始想办法使用各种工具定位这里想当然认为的堆外内存泄漏。

1.pmap使用

使用pmap命令查看内存分配情况

pmap -x 100750 > /export/Domains/pmap.txt

从这里可以看到00000001c0000000 这个地址很有问题,分配了9个g的rss。接下来有了这个地址,需要找到分配到这个地址的线程堆栈信息。

2.gperftools使用

perftools安装需要依赖:libunwind, 首先安装libunwind:

tar -xvf libunwind-1.2.1.tar.gz
cd libunwind-1.2.1
./configure --prefix=/target/libunwind
make
make install

tar -xvf gperftools-2.6.1.tar.gz

cd gperftools-2.6.1

./configure --prefix=/target/gperftools

make (安装时候, 可能会报g++: command not found, 切换到root: yum -y install gcc+ gcc-c++, 需要再次执行上一步, 否则会报异常)

make install

安装完成之后 加入环境变量然后再启动应用

export LD_PRELOAD=/target/gperftools/lib/libtcmalloc.so

export HEAPPROFILE=/logs/heap.hprof

export HEAP_PROFILE_ALLOCATION_INTERVAL=1073741824

正常来说,运行一段时间后 会生成多个文件。按照1个g进行分割文件。

如上图所示 运行一段时间后,只有很小的内存dump文件,那么得出结论显然不是使用malloc申请的的内存。如果分析怀疑是的话,可以针对怀疑的heap文件使用下面的命令:

/target/gperftools/bin/pprof --text /jdk1.8.0_91/bin/java  /logs/heap.hprof.0001.heap

可以分析具体调用链路。进而知道具体的调用位置。下面这个网上盗图。但是显然这里没有这个疑似内存和文件。

3. strace工具使用

既然知道00000001c0000000不是malloc分配的。那么有没有可能是直接使用mmap/brk申请的。接下来使用strace工具分析。

安装strace

yum install strace

按照进程ID进行收集mmap和brk的分配内存信息

strace -f -e"brk,mmap,munmap" -o /logs/strace.txt -p 26633

打开文件如下,第一列是线程ID,第二列是mmap方式,第三列是内存地址,第四列是RSS。

显然我在该文件也没找打0x0001c0000000所在地址的分配信息。假如找到的话,比如是上图的26972这个线程ID。那么十进制转十六进制。

得到0x695c。那么我们做jstack。可以从jdos获取,也可以自己输入命令。

jstack [ option ] pid

拿到文件后:找到0x695c的堆栈信息如下。进而就知道泄漏的位置如下。

但是显然这里我还是没找到mmap里面分配0x0001c0000000这个地址的地方。

4.gdb工具的使用

既然都查不到具体内存是哪分配的,那么先看看内存长什么样子的。

//开启gdb

gdb attach 181669

//dump 相关虚拟地址和偏移量的信息

dump memory /logs/dump.bin 0x00000001c0000000 0x00000001c0000000+25184512*1024

//输出

strings /export/Logs/dump.bin | less

得到如下信息:大概就是加载jar包等等信息,具体也分析不出来。很多乱码。

5.perf命令使用

//打断点 看内存记录

perf record -g -p 100750

//输出文件

perf report -i perf.data

得到如下:也不符合9个g的大小。并且没有可疑的位置。

3.怀疑数值

这个时候就很怀疑了,基本能想到的工具都使用了,还是定位不出来0x0001c0000000这个地址到底是什么?

回头想想参考下面的图片 这里堆内存很大,但是top命令的res只有9.7g,那么会不会数值有问题呢?

为什么top命令res和NMT 中的 committed差这么多呢?NMT的要高很多。

先输出下结论 :

JVM 中所谓的 commit 内存,只是将内存 mmaped 映射为可读可写可执行的状态!而在 Linux 中,在分配内存时又是 lazy allocation 的机制,只有在进程正访问时才分配真实的物理内存。所以 NMT 中所统计的 committed 并不是对应的真实的物理内存,自然与 RES 等统计方式无法对应起来。所以 JVM 为我们提供了一个参数 -XX:+AlwaysPreTouch,使我们可以在启动之初就按照内存页粒度都访问一遍 Heap,强制为其分配物理内存以减少运行时再分配内存造成的延迟(但是相应的会影响 JVM 进程初始化启动的时间。

接下来添加-XX:+AlwaysPreTouch再启动应用后进行top得到如下:

也就是说一启动应用就会res在25g左右,堆内存已经提前显示在这里了,不再是应用启动后增长型的。

那么我们就很容易得到结论查了半天的地址0x0001c0000000 应该是操作系统为jvm分配的堆的内存地址信息。

由于top命令的显示问题,让我误会了,应用启动时,res正在飙升。其实是没升的,一开始就是25g。

精简结论

1.top显示问题

top命令res的显示会有延迟,添加 -XX:+AlwaysPreTouch,才能正确得到显示的res。

2.内存泄漏结论

目前能确认的确存在,但是不在应用启动时,并且不明确是堆外还是堆内。包括jvm内还是jvm外。通过上述手段进行定位具体泄漏位置。包括NMT,pmap,gerftools,strace,gdb,perf等工具进行尝试。

参考文章

Spring Boot引起的“堆外内存泄漏”排查及经验总结 - 美团技术团队

Linux进程的内存使用情况 - Linux程序员 - SegmentFault 思否

Native Memory Tracking 详解(1):基础介绍 | HeapDump性能社区

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值