内存暴涨的分析方法和工具

glibc 的内存分配器有时会导致进程占用了过多的虚拟或物理内存。在 MySQL 的运行实例中多次发现内存占用过高的问题。一种比较常见的情况是因为 glibc 采用的 ptmalloc 算法会给每个 CPU 内核保留一定数目的 Arena,例如在 64 位机器上是 8 个。如果机器有 16 个 CPU 核,则总共可能会用到 16 * 8 = 128 个 Arena。每个的大小是 64MiB,这样就会占用 8GiB 的内存。在很多系统中都遇到了这个问题,例如 MemSQL1, Java 应用2等等。这种情况还好,至少占用的内存总量有个可以估计的上限。另外一种情况是触发了 glibc 的缺陷,导致内存碎片不断增加,从而占用越来越多的内存。碎片增加可以尝试用 malloc_trim() 来释放,不过这个调用开销较大,不能太频繁的调用。最好的办法还是避免触发这种 BAD CASE(说来容易做起来难),粗暴一点的搞法是换成 JEMALLOC 等回避问题。

分析方法和注意事项

实际分析内存占用的时候,有一些需要注意的地方:

  1. 优先排查确认程序没有内存泄露或大块的缓存等。

    这个可以借助 valgrind、编译器的 xSAN 等工具,或者依赖系统自己提供的分析工具。

  2. 注意区分虚拟内存和物理内存。

    ps 等命令输出的会有 VIRT 和 RSS 等不同的信息。虚拟内存地址空间在 64 位机器下很大,VIRT 很大很多时候并没有什么问题。当然具体情况还需要具体分析。

  3. 尽量采用不影响程序运行的工具分析,其次尝试在线 gdb 或 core 出来分析。

    将 stderr 重定向到文件。

    (gdb) call (void)close(2) 
    (gdb) call (int)open("/tmp/gdb.log", 2)
    

    获得 glibc 的统计信息并输出。

    (gdb) call (void)malloc_info()
    (gdb) call (int)fflush(0)
    

    当然也可以让 gdb 批处理。

    $ gdb --batch --pid 12345 --ex 'call malloc_info()'
    

    注意,malloc_stats 记录内存大小的字段是 int32,存在溢出问题,所以可能不准确。推荐的是 malloc_info 1

    malloc_trim() 可能可以释放掉过多的内存,例如:

    (gdb) call (void)malloc_trim(0)   
    

    malloc_trim 会通过 sbrk 和 madvise 来释放堆顶的空闲内存和 arena 中通过 mmap 分配来的内存。

分析工具

这里3有个关于内存占用的问题,其中提到了 PRELOAD 和 glibc 的内存分析工具等。这4 是另外一个关于内存占用的问题。

这里1 有个脚本统计 maps 里头各种类型的总字节数。

$ cat /proc/1234/maps | awk '{ split($1, a, "-"); b=strtonum("0x" a[1]); e=strtonum("0x" a[2]) } /stack/ { t="stack"; } /heap/ { t="heap"; } /\.so/ { t="so" } /\.mu/ { t="mu" } /\.mo/ { t="mo"; }  (!t) { $1 = ""; t=$0} { print e-b " " t ; t=""}' | awk '{ s=$1; $1=""; b[$0] += s; c[$0] += 1 } END { print "bytes count type"; for (t in b) { print b[t] " " c[t] " " t } }' | sort -nk 1

也可以用 perf 来 trace 系统调用以及调用栈:

$ sudo perf record -g -e syscalls:sys_enter_mmap --filter 'len > 60000000' --pid $PID -o perf.txt -- sleep $2_DAYS_IN_SECONDS

perf 用到的系统调用追踪名字。

$ ls -l /sys/kernel/debug/tracing/events/syscalls

strace5 可以用来打印函数调用堆栈,例如:

$ strace -k -e trace=%memory -o trace.txt ./a.out

不过系统默认的 strace 很可能是不支持 -k 开关的,需要自行下载源代码编译。

$ sudo yum install libunwind libunwind-devel
$ ./bootstrap
$ ./configure --enable-stacktrace=yes
$ make -j && sudo make install

Footnotes

1 http://codearcana.com/posts/2016/07/11/arena-leak-in-glibc.html

2 https://www.ibm.com/developerworks/community/blogs/kevgrig/entry/linux_glibc_2_10_rhel_6_malloc_may_show_excessive_virtual_memory_usage?lang=en

3 https://sourceware.org/bugzilla/show_bug.cgi?id=21731

4 https://sourceware.org/bugzilla/show_bug.cgi?id=15321

5 https://strace.io

[ memory ]
Written on April 18, 2019