非JVM管理内存分析
前面说明了JVM管理内存的分析,主要是是依靠java提供的一些工具进行分析。下面会讲一下怎么对非JVM管理的内存进行分析
分析进程的内存
进程内存明细
通过pmap -X <pid>
命令可以查看进程内存明细,包含内存起始地址(Address)、权限(Perm)、偏移量(Offset)、Rss内存(Rss)、映射文件(Mapping)。pmap命令是对/proc/<pid>/smaps
文件的解析,系统没有安装pmap命令可以自行对smaps文件进行解析。
内存明细中Offset不为0或者Device不为00:00的记录,是表示文件映射。排除掉文件映射和---p权限的内存,剩下的内存就是进程独占的Rss内存了。
起始地址是Address,结束地址等于Address + Size字节的16进制。比如:Address=7f6f1c000000,Size=132KB,Size字节的16进制=21000,所以结束地址=7f6f1c021000
通过
cat /proc/<pid>/maps
也可以知道内存块的起始地址和结束地址。
进程cache内存
进程使用的文件会使用cache方式缓存部分文件内容到内存中。比较常见的是,运行中的jar包会缓存到内存中、rocketmq的commitlog也会缓存在内存中。 那怎么查看cache内存的使用情况呢?可以使用工具:pgcacher 使用方法:
# 下载工具pgcache
wget xiaorui-cc.oss-cn-hangzhou.aliyuncs.com/files/pgcacher
# 给工具授权
chmod +x pgcacher
# 查看进程使用到的cache内存
./pgcacher -pid <pid>
执行命令后,输出以下内容,Size是文件大小;Pages是页数,一般1页4K大小;Cached Size是文件缓存到内存中的大小,不一定整个文件都缓存,可能只缓存一部分;Cached Pages是缓存的页数;Percent是缓存大小占总文件大小的比例,100表示全部缓存。
下面是rocketmq使用到的cache情况,commitlog是1G大小,可以看到有99%的内容缓存到了cache中:
分析内存内容
分析内存问题的时候,如果能知道内存是什么内容,就可以为排查问题提供思路。 操作方法:
# 判断是否安装了gdb
gdb -v
# 安装gdb
yum install gdb
# dump出内存镜像,地址前要加0x
gdb --batch --pid <pid> \
-ex "dump memory ./1.dump 起始地址 结束地址"
# 将dump出来的镜像转为字符串
strings 1.dump > 1-strings.txt
比如,对Address=7f6f1c000000,结束地址=7f6f1c021000,这段地址范围的内存进行分析
gdb --batch --pid 665 \
-ex "dump memory ./1.dump 0x7f6f1c000000 0x7f6f1c021000"
# 只输出每行长度大于等于4个字符的内容
strings 1.dump > 1-strings.txt
# 只输出每行长度大于等于10个字符的内容
strings -n 10 1.dump > 1-strings.txt
如何找到有问题的内存块
分析内存前后差异
- 在程序出问题前打印一下内存明细:
pmap -X <pid> > pid-pmap-1.txt
- 在程序出现内存问题的时候,再打印一次pmap:
pmap -X <pid> > pid-pmap-2.txt
- 对比两次文件的差异:
结合NMT分析
NMT是JVM Native Memory Tracking,结合NMT可以精准定位非JVM管理内存。 NMT默认是关闭的,jvm启动时添加以下参数,可以开启NMT:
-XX:NativeMemoryTracking=detail
分析步骤:
1、使用pmap打印进程内存明细
2、执行jcmd <pid> VM.native_memory detail
命令,获取到JVM内存的地址范围
3、排除掉所有JVM内存的地址范围,那么剩下的就是非JVM管理的内存了。
4、通过gdb以及strings命令就可以分析出非JVM管理内存里的内容。
那如何筛选出第3步中的地址范围呢?毕竟地址比较多,手工操作会比较慢,比较繁琐。我这边写了一个小工具,用于筛选:AnalysisMemDiff
分析案例
下面通过一个案例来实践一下上面提到的分析方法
zip内存泄漏
我简化了一下我遇到的问题,用下面的代码来模拟zip内存泄漏的问题:
环境准备
用一个压缩文件 test.txt.gz,里面包含一个test.txt文件,文件内容是:test-hzj
复现步骤
- 运行程序
- 打印内存映射
- 执行解压
- 再次打印内存映射
- 对比前后两次pmap文件,发现多了许多64M的内存空间
- 通过gdb命令输出内存里的内容并查看内容
gdb --batch --pid 13547 \
-ex "dump memory ./memory-13547.dump 0x7f03c8000000 0x7f03cbffd000"
strings memory-13547.dump > dump-strings-13547.txt
内容如下:
……
test-hzj
test-hzj
test-hzj
test-hzj
test-hzj
x@Az
test-hzj
?Q:z
`JC&z
204z
S;Kz
)U,z
test-hzj
test-hzj
……
- 发现内存里面很多是压缩文件里的内容
ZipUtil代码中Zip流没有关闭。由于Zip流底层使用了native方法,Zip流不关闭会导致内存泄漏。
附录
NMT操作命令
# 查看jvm内存汇总
jcmd <pid> VM.native_memory summary
# 设置内存基线,后续可以通过命令和基线的内存进行对比
jcmd <pid> VM.native_memory baseline
# 与内存基线对比差异
jcmd <pid> VM.native_memory summary.diff
# 查看jvm内存明细,包含内存地址
jcmd <pid> VM.native_memory detail
例子:
内存汇总:
内存汇总对比差异:
内存明细:
转载自:https://juejin.cn/post/7321979919674654774