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

Joh*_*n S 6 c++ linux memory-leaks glibc

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

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

通过调试器进一步挖掘,我发现__run_exit_handlersmain. 我可以逐步执行各种调用,free因为它通过全局析构函数链工作。完成这些之后,我只观察到明显内存使用量的最小向下变化。然后,最后它调用_exit(),然后才立即将内存恢复到操作系统。

任何人都可以向我提供有关如何进行调试的其他提示吗?为什么我的程序不还给那个内存?

Sta*_*irl 6

这里的一切都基于malloc在 Linux上运行的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]);
}
Run Code Online (Sandbox Code Playgroud)

看起来 glibc 根本没有放弃内存。根据mallopt(3)手册页,参数M_TRIM_THRESHOLD负责放弃内存。默认情况下它是 128kb,而测试程序分配和释放 5 GB 的内存。看起来其他一些malloc实现细节不允许它释放内存。

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

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

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

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