glibc应用程序保持未使用的内存,直到退出之前

时间:2018-02-06 20:28:29

标签: c++ linux memory-leaks glibc

我有一个在Linux(Centos 7)上运行的C ++应用程序(gcc 4.9.1,glibc 2.17)。它使用各种第三方库,特别是Boost 1.61。当应用程序运行时,我可以通过htop的VIRT和RES列或者ps命令等来观察其内存使用量的稳定增长。如果我让它足够长,它将使用大量的内存并淹没盒子。

听起来像是一个漏洞,但它传递的valgrind只有几个字节泄漏,所有这些都在我期望的地方。调试打印消息表明程序流程符合预期。

通过调试器进一步挖掘,我发现在__run_exit_handlers结束时调用main时,大部分内存仍在使用中。我可以逐步调用free,因为它通过全局析构函数链工作。完成后,我观察到表观内存使用量只有极小的向下变化。然后,最后它调用_exit(),然后才将内存恢复到操作系统,一次性完成。

有人可以向我提供有关如何进行调试的其他提示吗?为什么我的程序不能回忆这段记忆?

2 个答案:

答案 0 :(得分:2)

此处的所有内容都基于在Linux上运行的malloc的GNU libc实现。

下面的测试程序在释放内存后似乎没有给系统留下任何内存(strace没有显示将内存返回内核的sbrk调用):

int main()
{
    static const int N = 5000000;
    static void *arr[N];

    for (int i = 0; i < N; i++)
        arr[i] = std::malloc(1024);

    // reverse to simplify allocators job
    for (int i = N - 1; i >= 0; i--)
        std::free(arr[i]);
}

看起来glibc根本没有放弃记忆。根据{{​​3}}手册页,参数M_TRIM_THRESHOLD负责释放内存。默认情况下它是128kb,而测试程序分配并释放5 GB内存。看起来malloc实现的其他一些细节不会让它释放内存。

目前我可以推荐以下解决方案:

  • 如果可以,请尝试偶尔或在释放大量内存后调用mallopt(3)。这应该强制修剪,并且应该使用MADV_DONTNEED将内存返回给操作系统。
  • 避免使用mallocoperator new分配大量小对象,而是从大小超过M_MMAP_THRESHOLD的内存池中分配它们。如果程序逻辑允许,请尝试在之后销毁该池。大小大于M_MMAP_THRESHOLD的内存块会立即释放回操作系统。
  • 与上一个相同,但应该更快:使用mmap为小对象分配内存池,并使用malloc_trimMADV_DONTNEED / MADV_FREE将内存释放回操作系统。
  • 尝试使用另一个可能利用MADV_FREE的分配器将内存返回给系统(jemalloc?)。

我在glibc的bugzilla上找到了madvise旧(2006)票。它说free永远不会将内存返回给内核,除非调用malloc_trim

free的较新版本似乎有执行内部systrim函数的代码,该代码应修剪竞技场的顶部,但我无法使其正常工作。

答案 1 :(得分:0)

您可以使用valgrind --tool=massif ./executable

分析内存分配

查看http://valgrind.org/docs/manual/ms-manual.html

上的文档

然后,一旦分析了数据,就可以应用内存池和其他技术。既然你已经使用了Boost,你可以在Boost中找到几个这样的工具。