内存暴涨的分析方法和工具
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 等回避问题。
分析方法和注意事项
实际分析内存占用的时候,有一些需要注意的地方:
-
优先排查确认程序没有内存泄露或大块的缓存等。
这个可以借助 valgrind、编译器的 xSAN 等工具,或者依赖系统自己提供的分析工具。
-
注意区分虚拟内存和物理内存。
ps 等命令输出的会有 VIRT 和 RSS 等不同的信息。虚拟内存地址空间在 64 位机器下很大,VIRT 很大很多时候并没有什么问题。当然具体情况还需要具体分析。
-
尽量采用不影响程序运行的工具分析,其次尝试在线 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
memory
]