glibc 应用程序一直占用未使用的内存,直到退出前

Posted

技术标签:

【中文标题】glibc 应用程序一直占用未使用的内存,直到退出前【英文标题】:glibc application holding onto unused memory until just before exit 【发布时间】:2018-07-17 00:18:31 【问题描述】:

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

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

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

谁能提供有关如何继续调试的其他提示?为什么我的程序不回馈那段内存?

【问题讨论】:

你怎么知道这是内存泄漏,而不是你的应用程序只是内存密集型的? 我不认为这是泄漏。请注意,在我的应用程序代码运行完成后,此内存仍在“使用中”,并且至少根据 valgrind 已释放它要求的所有内存。 @Ivan 我根本没有分配很多对象,但我正在使用的 3p 库之一可能是。我将不得不在那里更深入地挖掘。 另一个question 关于这个。 请注意,虽然从技术上讲这不是泄漏,但可以继续使用越来越多的内存。也许程序忘记释放它。也许内存碎片意味着大量无法重用的小空闲块。 【参考方案1】:

这里的一切都基于在 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 根本不会放弃内存。根据mallopt(3) 手册页,参数M_TRIM_THRESHOLD 负责放弃内存。默认情况下它是 128kb,而测试程序分配和释放 5 GB 内存。看起来malloc 实现的其他一些细节并没有让它释放内存。

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

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

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

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

【讨论】:

这很有帮助。定期调用malloc_trim 大大减少了应用程序使用的 RES 内存量,但没有减少 VIRT。我没有在我自己的代码中分配很多对象,所以我使用的外部库之一必须是。我将不得不对它们进行分析,看看我是否可以修改它们以在正确的位置使用池分配器。 @JohnS 虚拟内存应该不是什么大问题。 malloc_trim 的问题在于它可能根本不会释放任何虚拟内存,而是在空闲区域上使用madviseMADV_DONTNEED,替换未使用的零页页面。因此操作系统会取回其内存,但地址空间的更改需要时间,从而降低性能。 MADV_FREE 做了类似的事情,但应该更快,因为它只在内存压力下重新映射页面。 修整阈值适用于单次分配。如果应用程序使用 5 GB 的 128 字节分配,则不会免费修剪。 @ZanLynx 这意味着由于M_MMAP_THRESHOLDM_TRIM_THRESHOLD 默认为 128kb,因此对于小分配,从不使用默认参数释放内存。 @Ivan:没错。假设未来的分配将重用这些空闲内存位。【参考方案2】:

您可以使用valgrind --tool=massif ./executable 分析您的内存分配

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

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

【讨论】:

以上是关于glibc 应用程序一直占用未使用的内存,直到退出前的主要内容,如果未能解决你的问题,请参考以下文章

网络未准备好或其他程序占用控制端口,请退出程序检查IP配置后再试!!

Linux 多线程编程--线程退出

SQL Server 2008 R2 持续占用内存直到服务器死机,怎么解决?

linux 下怎么优化mysql占用内存?

Linux系统编程——特殊进程之僵尸进程

如何查看mysql内存占用原因