【问题标题】:memory leak analyse with top内存泄漏分析与顶部
【发布时间】:2018-12-05 13:57:29
【问题描述】:

我想使用“top”工具来分析一个进程的内存消耗和可能的内存泄漏。 为此我编写了这个程序(程序名:memoryTest):

int main(){
char* q;
for(int i=0; i<100; i++){
    q = (char*) malloc(1024); 
    sleep(1);
}
return 0;   

}

有了top我现在可以看这个节目了, 通过在上述过程之后使用选项“o”和过滤器规范“COMMAND = memoryTest”进行过滤,但是我发现该进程的内存消耗没有变化。 我这里有一个愚蠢的错误吗?

【问题讨论】:

  • 您的运行时/操作系统可能很聪明,实际上并没有分配您请求但未使用的内存。尝试将内容写入内存。
  • 您的编译器进行了优化,malloc 调用已从最终代码中删除。
  • @TungLeThanh 我不完全确定这是否是有效的优化。即使在运行时,您也可以将malloc 调用“重定向”到您自己的带有副作用的函数(例如,通过LD_PRELOAD),编译器在编译时无法知道。相关讨论:stackoverflow.com/q/53373421/580083
  • @DanielLangr:我观察到在编译给定应用程序并激活 -O1(通过 gcc)时的行为。
  • @TungLeThanh 你说得对,理由是malloc 不是一个外部函数,而是一个没有副作用的库函数。因此允许编译器优化它的调用。

标签: c++ memory-leaks memoryanalyzer


【解决方案1】:

来自 malloc 手册页:

通常,malloc() 从堆中分配内存,并调整 使用 sbrk(2) 根据需要设置堆的大小。分配块时 大于 MMAP_THRESHOLD 字节的内存,glibc malloc() 实现将内存分配为私有匿名映射 使用 mmap(2)。 MMAP_THRESHOLD 默认为 128 kB,但 使用 mallopt(3) 进行调整。在 Linux 4.7 之前的分配 使用 mmap(2) 执行的操作不受 RLIMIT_DATA 资源的影响 限制;自 Linux 4.7 起,此限制也适用于分配 使用 mmap(2) 执行。

内存池称为 arenas,实现在 arena.c 中。 宏 HEAP_MAX_SIZE 定义了一个 arena 的最大大小,基本上 32 位为 1MB,64 位为 64MB:

HEAP_MAX_SIZE = (2 * DEFAULT_MMAP_THRESHOLD_MAX)
32-bit [DEFAULT_MMAP_THRESHOLD_MAX = (512 * 1024)] = 1,048,576 (1MB)
64-bit [DEFAULT_MMAP_THRESHOLD_MAX = (4 * 1024 * 1024 * sizeof(long))] = 67,108,864 (64MB)

来自堆实现的信息 (arena.c):

/* 堆是一个单独的连续内存区域,保存(可合并)malloc_chunks。它是使用 mmap() 分配的,并且始终从与 HEAP_MAX_SIZE 对齐的地址开始。 */

编辑:

可以使用 strace 观察堆分配。在第一次调用 brk() 时,主 arena 分配了 200K 字节(libstdc++ 中的 72K 和 128K top_pad )。

brk(NULL)                               = 0x556ecb423000 -> current program break
brk(0x556ecb455000)                     = 0x556ecb455000 -> resize the heap by moving brk 0x32000 bytes upward (main arena initialization with 200K). 
write(1, "i = 0\n", 8)                = 8
...
write(1, "i = 123\n", 8)                = 8     
brk(0x556ecb476000)                     = 0x556ecb476000 -> resize the heap by moving brk 0x21000 bytes upward (growing heap 128K). 
...
write(1, "i = 252\n", 8)                = 8
brk(0x556ecb497000)                     = 0x556ecb497000 -> resize the heap by moving brk 0x21000 bytes upward (growing heap 128K). 

您的应用程序仅使用了 100K 字节的 128K 可用堆,因此 top 或 htop 程序不会观察到内存消耗。

如果您通过请求大于 128K 的块或增加块数 ( > 128 ) 来强制 glibc 使用 mmap(),您可以轻松看到内存消耗的变化。

【讨论】:

    【解决方案2】:

    尝试以下方法:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    
    int main(){
      char* q;
      for(int i=0; i<10000; i++){
        q = (char*)malloc(1024); 
        memset(q,0,1024);
      }
      getchar();
      return 0;
    }
    

    for 循环中的不同值。

    这里的问题是 Linux 不需要使用内存,即使它是在实际填充之前分配的。因此,您需要将数据写入要分配的内容,否则它甚至可能不会记录该内存正在使用中。这就是为什么有的应用程序可以分配内存就可以了,那么即使已经分配了,当他们来使用内存时,他们会发现内存不可用。

    memset 将强制将零写入分配的缓冲区,从而导致内存被使用并注册为在顶部使用。请注意,在这里使用 htop 可能更容易。

    如果您想进一步了解作为 Linux 特性的“乐观 malloc”,请注意其他一些操作系统的行为方式并非如此。

    另外值得指出的是,内部内存是按一定大小的连续块分配的。因此,如果以 4k 的最小块大小分配内存,那么分配额外的 1KB 可能不会增加大小。

    【讨论】:

      【解决方案3】:

      如果您想了解内存使用情况,请不要使用 top。使用免费的开源程序https://github.com/vmware/chap(免责声明:我是原始开发者)。

      只需在程序结束前收集一个实时内核,例如通过运行 gcore,然后键入:

      章节你的核心文件名

      然后您可以执行以下操作:

      计数泄露 名单泄露 使用的计数 不计较 ....

      这是一个使用您的程序的示例,在 30 秒后收集核心:

      -bash-4.1$ ./q53633998 &
      [1] 18014
      -bash-4.1$ sleep 30
      gcore 18014
      -bash-4.1$ gcore 18014
      0x00000030ed6aca20 in __nanosleep_nocancel () at ../sysdeps/unix/syscall-         template.S:82
      82      T_PSEUDO (SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS)
      Saved corefile core.18014
      -bash-4.1$ ~/public_html/chap core.18014
      chap> count used
      36 allocations use 0x9120 (37,152) bytes.
      chap> count free
      1 allocations use 0x17db0 (97,712) bytes.
      chap> count leaked
      35 allocations use 0x8d18 (36,120) bytes.
      chap> count anchored
      1 allocations use 0x408 (1,032) bytes.
      chap> list anchored
      Used allocation at 19d4e40 of size 408
      
      1 allocations use 0x408 (1,032) bytes.
      chap> explain 19d4e40
      Address 19d4e40 is at offset 0 of
      an anchored allocation at 19d4e40 of size 408
      Allocation at 19d4e40 appears to be directly anchored from at least one stack.
      Address 0x7ffc88570270 is on the live part of the stack for thread 1.
      Stack address 7ffc88570270 references 19d4e40
      
      chap> list leaked
      Used allocation at 19cc010 of size 408
      
      Used allocation at 19cc420 of size 408
      
      Used allocation at 19cc830 of size 408
      
      Used allocation at 19ccc40 of size 408
      
      Used allocation at 19cd050 of size 408
      
      Used allocation at 19cd460 of size 408
      
      Used allocation at 19cd870 of size 408
      
      Used allocation at 19cdc80 of size 408
      
      Used allocation at 19ce090 of size 408
      
      Used allocation at 19ce4a0 of size 408
      
      Used allocation at 19ce8b0 of size 408
      
      Used allocation at 19cecc0 of size 408
      
      Used allocation at 19cf0d0 of size 408
      
      Used allocation at 19cf4e0 of size 408
      
      Used allocation at 19cf8f0 of size 408
      
      Used allocation at 19cfd00 of size 408
      
      Used allocation at 19d0110 of size 408
      
      Used allocation at 19d0520 of size 408
      
      Used allocation at 19d0930 of size 408
      
      Used allocation at 19d0d40 of size 408
      
      Used allocation at 19d1150 of size 408
      
      Used allocation at 19d1560 of size 408
      
      Used allocation at 19d1970 of size 408
      
      Used allocation at 19d1d80 of size 408
      
      Used allocation at 19d2190 of size 408
      
      Used allocation at 19d25a0 of size 408
      
      Used allocation at 19d29b0 of size 408
      
      Used allocation at 19d2dc0 of size 408
      
      Used allocation at 19d31d0 of size 408
      
      Used allocation at 19d35e0 of size 408
      
      Used allocation at 19d39f0 of size 408
      
      Used allocation at 19d3e00 of size 408
      
      Used allocation at 19d4210 of size 408
      
      Used allocation at 19d4620 of size 408
      
      Used allocation at 19d4a30 of size 408
      
      35 allocations use 0x8d18 (36,120) bytes.
      chap> list free
      Free allocation at 19d5250 of size 17db0
      
      1 allocations use 0x17db0 (97,712) bytes.
      chap>
      

      最后一次“空闲”分配是在第一次 malloc 调用期间分配的内存块的尾部,并随着后续 malloc 调用的进行逐渐被瓜分。

      当然,还有其他工具(例如 valgrind)可以通过检测进程来实现不同的工作方式,但是如果您想要一个可以在不改变进程运行方式的情况下分析进程的内存使用情况的工具,那么 chap 是一个不错的选择.

      【讨论】:

        猜你喜欢
        • 2013-05-24
        • 1970-01-01
        • 1970-01-01
        • 2012-05-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多