glibc应用程序保留未使用的内存直到退出 我有一个C++应用程序(GCC 4.91,GLUBC 2.17),它运行在Linux上(CCENS 7)。它使用各种第三方库,尤其是Boost 1.61。当应用程序运行时,我可以通过htop的VIRT和RES列,或者ps命令等看到它的内存使用量稳步增加。如果我让它运行足够长的时间,它将使用大量的内存,并淹没这个盒子

glibc应用程序保留未使用的内存直到退出 我有一个C++应用程序(GCC 4.91,GLUBC 2.17),它运行在Linux上(CCENS 7)。它使用各种第三方库,尤其是Boost 1.61。当应用程序运行时,我可以通过htop的VIRT和RES列,或者ps命令等看到它的内存使用量稳步增加。如果我让它运行足够长的时间,它将使用大量的内存,并淹没这个盒子,c++,linux,memory-leaks,glibc,C++,Linux,Memory Leaks,Glibc,听起来像是泄漏,但它只泄漏了几个字节就通过了valgrind,所有这些都在我预期的地方。调试打印消息表明程序流符合预期 通过调试器进一步挖掘,我发现在main末尾调用\uu run\u exit\u处理程序时,大部分内存仍在使用中。我可以单步调用free,因为它通过全局析构函数链工作。在它完成这些之后,我只观察到明显的内存使用率有一个最小的向下变化。然后,最后它调用\u exit(),只有这样,内存才能立即恢复到操作系统中 有人能给我提供关于如何继续调试这个的额外提示吗?为什么我的程序不把内存还

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

通过调试器进一步挖掘,我发现在
main
末尾调用
\uu run\u exit\u处理程序时,大部分内存仍在使用中。我可以单步调用
free
,因为它通过全局析构函数链工作。在它完成这些之后,我只观察到明显的内存使用率有一个最小的向下变化。然后,最后它调用
\u exit()
,只有这样,内存才能立即恢复到操作系统中


有人能给我提供关于如何继续调试这个的额外提示吗?为什么我的程序不把内存还给我?

这里的一切都是基于Linux上运行的
malloc
的GNU libc实现的

释放内存后,下面的测试程序似乎不会向系统释放任何内存(
strace
不显示将内存返回内核的
sbrk
调用):

intmain()
{
静态常数int N=5000000;
静态空隙*arr[N];
对于(int i=0;i=0;i--)
标准:免费(arr[i]);
}
看起来glibc根本就不会把记忆还给别人。根据手册页,参数
M_TRIM_THRESHOLD
负责释放内存。默认值为128kb,而测试程序分配并释放5GB内存。看起来像是
malloc
实现的一些其他细节,但不允许释放内存

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

  • 如果可以的话,试着偶尔打个电话,或者在释放大量内存后再打。这将强制进行微调,并使用
    MADV\u DONTNEED
    将内存返回给操作系统
  • 避免使用
    malloc
    operator new
    分配大量小对象,而是从大于
    M_map_阈值的内存池中分配它们。如果程序逻辑允许,请尝试在之后取消该池的运行。大于
    mmmap_阈值的内存块将立即释放回操作系统
  • 与前一个相同,但应该更快:使用
    mmap
    为小对象分配内存池,并使用和
    MADV\u DONTNEED
    /
    MADV\u FREE
    将内存释放回操作系统
  • 尝试使用另一个可能利用
    MADV_FREE
    将内存返回系统的分配器(jemalloc?)

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


较新版本的
free
似乎具有执行内部
systrim
功能的代码,该功能应该可以修剪舞台顶部,但我无法使其工作。

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

查看以下网址的文档:


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

您如何知道这是内存泄漏,而不是您的应用程序只是内存密集型的?我不认为这是泄漏。请注意,在我的应用程序代码运行完毕后,该内存仍在“使用中”,至少根据valgrind的说法,该内存已释放了它所需的所有内存。@Ivan我根本没有分配很多对象,但我正在使用的3p库之一可能是。我将不得不在这方面进行更深入的挖掘。关于这方面的另一个问题。请注意,虽然从技术上讲这并不是一个漏洞,但仍有可能继续使用越来越多的内存。也许程序忘了释放它。也许内存碎片意味着大量无法重用的小空闲块。这很有帮助。定期调用malloc_trim
大大减少了应用程序使用的RES内存量,但没有减少VIRT。我没有在自己的代码中分配很多对象,所以我使用的一个外部库必须是。我必须对它们进行分析,看看是否可以修改它们,以便在适当的位置使用池分配器。@JohnS虚拟内存应该不是什么大问题。
malloc_trim
的问题是可能根本无法释放任何虚拟内存,相反,它将在空闲区域上使用
madvise
MADV_DONTNEED
,将未使用的页面替换为零页面。因此,操作系统可以收回内存,但地址空间的更改需要时间,从而降低了性能
MADV_FREE
也有类似的功能,但应该更快,因为它只在内存压力下重新映射页面。修剪阈值适用于单个分配。如果应用程序使用5 GB的128字节分配,则不会免费进行修剪。@ZanLynx这意味着由于
M_MMAP_THRESHOLD
默认值为128kb,以及
M_TRIM_THRESHOLD
,因此对于小的分配,内存永远不会使用默认参数释放。@Ivan:没错。假设将来的分配将重用这些空闲内存位。
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]);
}