likes
comments
collection
share

非JVM管理内存分析

作者站长头像
站长
· 阅读数 6

前面说明了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 非JVM管理内存分析 通过cat /proc/<pid>/maps 也可以知道内存块的起始地址和结束地址。 非JVM管理内存分析

进程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表示全部缓存。 非JVM管理内存分析 下面是rocketmq使用到的cache情况,commitlog是1G大小,可以看到有99%的内容缓存到了cache中: 非JVM管理内存分析

分析内存内容

分析内存问题的时候,如果能知道内存是什么内容,就可以为排查问题提供思路。 操作方法:

# 判断是否安装了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

如何找到有问题的内存块

分析内存前后差异

  1. 在程序出问题前打印一下内存明细:pmap -X <pid> > pid-pmap-1.txt
  2. 在程序出现内存问题的时候,再打印一次pmap:pmap -X <pid> > pid-pmap-2.txt
  3. 对比两次文件的差异:

非JVM管理内存分析

结合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 非JVM管理内存分析 复现步骤

  1. 运行程序

非JVM管理内存分析

  1. 打印内存映射

非JVM管理内存分析

  1. 执行解压

非JVM管理内存分析

  1. 再次打印内存映射

非JVM管理内存分析

  1. 对比前后两次pmap文件,发现多了许多64M的内存空间

非JVM管理内存分析

  1. 通过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
……
  1. 发现内存里面很多是压缩文件里的内容

ZipUtil代码中Zip流没有关闭。由于Zip流底层使用了native方法,Zip流不关闭会导致内存泄漏。 非JVM管理内存分析

附录

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

例子:

内存汇总:

非JVM管理内存分析

内存汇总对比差异:

非JVM管理内存分析

内存明细:

非JVM管理内存分析