内存问题总结

内存问题总结

写作目的:总结下工作中遇到的内存问题,整理下收集的资料

        之前工作中遇到服务器死机的情况,检查服务器没有core文件,应该不是crash引起的,
之后查看了服务器log(/var/log/memssage),out_of_memory 和 invoked oom-killer等信息,感到很奇怪,因为之前已经用valgrind(Intel Parallel Studio xe )做过内存泄露的检测,所以很肯定不是内存泄露引起的。

在持续调查中发现以下两点:

1:

使用pmap -x 16284 (16284是进程id) 查看进程的内存映像信息(pmap的使用)

下面数据很有规律,每两行为一组,各组相加的值刚好是65536KB(64M)
00007fefb4000000     132       4       4 rw---    [ anon ]
00007fefb4021000   65404       0       0 -----    [ anon ]
00007fefb8000000    1588    1432    1432 rw---    [ anon ]
00007fefb818d000   63948       0       0 -----    [ anon ]
00007fefbc000000     132      16      16 rw---    [ anon ]
00007fefbc021000   65404       0       0 -----    [ anon ]

感觉这边有问题,搜索了资料,发现原因是这样的:

是RHEL6(和Centos 6.4同源)里glibc采用了新的arena内存分配算法来提高多进程应用的内存分配性能,glibc里相比老的实现多个进程共用一个堆,新实现里可以保证每个线程都有一个堆,这样避免内存分配时需要额外的加锁来降低性能,而上面的环境变量则可以配置进程里的glibc使用指定数量的arena堆,避免分配过多的堆导致过多的内存使用,而根据glibc的代码,一个64位进程最多arena堆数是 8 × CPU核数。每个堆的大小可以从arena.c中看到:

#ifndef HEAP_MAX_SIZE
    #ifdef DEFAULT_MMAP_THRESHOLD_MAX
        #define HEAP_MAX_SIZE (2 * DEFAULT_MMAP_THRESHOLD_MAX)
    #else
        #define HEAP_MAX_SIZE (1024 * 1024) /* must be a power of two */
    #endif
#endif

DEFAULT_MMAP_THRESHOLD_MAX是系统定义的,其值可以man mallopt查到定义:
The lower limit for this parameter is 0. The upper limit is DEFAULT_MMAP_THRESHOLD_MAX: 5121024 on 32-bit systems or 410241024sizeof(long) on 64-bit systems.
因此64位系统上这个HEAP_MAX_SIZE的值就是 2 * 410241024sizeof(long) = 64M*了
问题的根源算找到了,是RHEL6(CentOS6)自带的glibc引起的,

解决方法

就是用上面的环境变量来控制最大的arena堆数目,或者选用由Google开发的更适合多线程环境下的tcmalloc

参考资料:

       JAVA 进程在64位LINUX下占用巨大内存的分析

       Why do some applications use significantly more virtual memory on RHEL 6 compared to RHEL 5

2:

可是我的问题,还没有解决,还有其他其他的问题引起内存占用巨大,说明下个问题前先介绍下内存吧

通过命令cat /proc/28361/status | grep Vm,得到如下输出

VmPeak:   587496 kB
VmSize:   491208 kB
VmLck:         0 kB
VmHWM:    261508 kB
VmRSS:    175520 kB
VmData:   456956 kB
VmStk:        88 kB
VmExe:       592 kB
VmLib:      4732 kB
VmPTE:       596 kB
VmSwap:        0 kB

       在这里我们关注VmSize|VmRSS|VmData|VmStk|VmExe|VmLib 这个6个指标,下面有一些简单的解释。

  VmSize(KB):虚拟内存大小。整个进程使用虚拟内存大小,是VmLib, VmExe, VmData, 和 VmStk的总和。

  VmRSS(KB):虚拟内存驻留集合大小。这是驻留在物理内存的一部分。它没有交换到硬盘。它包括代码,数据和栈。(我们光malloc了一篇内存,但是没有访问他是不会算到VmRSS中的,Linux内存分配有个预分配的概念)

  VmData(KB):程序数据段的大小(所占虚拟内存的大小),堆使用的虚拟内存。

  VmStk(KB):任务在用户态的栈的大小,栈使用的虚拟内存

  VmExe(KB):程序所拥有的可执行虚拟内存的大小,代码段,不包括任务使用的库

  VmLib(KB):被映像到任务的虚拟内存空间的库的大小

       VmSize = VmData + VmStk + VmExe + VmLib

上面解决了glibc引入64M的问题后,我在跑了一遍程序,发现64M的问题确实没有出现,但是还有一大片内存没有释放,
我做了如下处理:

  • 通过pmap查看进程的内存映像信息
  • 根据内存的地址和size(类似下面的,单位是KB),使用gdb + dump,把内存dump出来,发现里面的看懂的信息全是应该被释放掉的,不应该还驻留在内存,然后以搜索,发现有时glibc的问题。
    00007fefb4000000 132 4 4 rw— [ anon ]

问题原因如下:

       程序如果直接向操作系统申请内存的话,性能很低,因此将glibc置于两者之间解决这个问题,每次调用malloc的时候,默认情况下只有小于128kB内存由gblic返回,free内存的时候,只是还给glibc管理,只有头chunk释放的时候,才会还给操作系统;大于128KB时会通过mmap(很耗CPU)分配,释放通过munmap直接还给操作系统。

详细资料:

       频繁分配释放内存导致的性能问题的分析 由于使用mmap导致性能下降

       glibc下的内存管理 介绍glibc的内存分配管理策略

解决方法:

由于我们代码的很多模块都是为客户端程序使用的,而且已经存在很多年了,之前没有采用内存池来避开长时间运行
引起内存增长的问题;改写代码的代价相对较大;有考虑过自己换掉malloc/free函数,但是编写稳定的实现难度很大,
因此我们采用了jemalloc这种通用的内存池来缓解内存增长的问题。

jemalloc:

提到内存池的话,apache和nginx的内存池都是值得一看的

你可能感兴趣的:(内存问题总结)