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。随着应用程序的运行,我可以通过htop
的VIRT
和RES
列或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
将内存归还给操作系统。
避免使用malloc
或operator new
分配大量小对象,而是从大小大于M_MMAP_THRESHOLD
的内存池中分配它们。如果程序逻辑允许,请尝试在之后销毁该池。大于M_MMAP_THRESHOLD
的内存块会立即释放回操作系统。
与上一个相同,但应该更快:使用 mmap
为小对象分配内存池,并使用 madvise
和 MADV_DONTNEED
/MADV_FREE
将内存释放回操作系统。
尝试使用可能利用MADV_FREE
将内存返回给系统的另一个分配器(jemalloc?)。
我在 glibc 的 bugzilla 上找到了 this old (2006) 票证。它在那里说free
永远不会将内存返回给内核,除非调用malloc_trim
。
较新版本的free
似乎具有执行内部systrim
函数的代码,该函数应该修剪竞技场顶部,但我无法使其工作。
【讨论】:
这很有帮助。定期调用malloc_trim
大大减少了应用程序使用的 RES 内存量,但没有减少 VIRT。我没有在我自己的代码中分配很多对象,所以我使用的外部库之一必须是。我将不得不对它们进行分析,看看我是否可以修改它们以在正确的位置使用池分配器。
@JohnS 虚拟内存应该不是什么大问题。 malloc_trim
的问题在于它可能根本不会释放任何虚拟内存,而是在空闲区域上使用madvise
和MADV_DONTNEED
,替换未使用的零页页面。因此操作系统会取回其内存,但地址空间的更改需要时间,从而降低性能。 MADV_FREE
做了类似的事情,但应该更快,因为它只在内存压力下重新映射页面。
修整阈值适用于单次分配。如果应用程序使用 5 GB 的 128 字节分配,则不会免费修剪。
@ZanLynx 这意味着由于M_MMAP_THRESHOLD
和M_TRIM_THRESHOLD
默认为 128kb,因此对于小分配,从不使用默认参数释放内存。
@Ivan:没错。假设未来的分配将重用这些空闲内存位。【参考方案2】:
您可以使用valgrind --tool=massif ./executable
分析您的内存分配
查看http://valgrind.org/docs/manual/ms-manual.html的文档
然后,一旦您有了分析数据,您就可以应用内存池和其他技术。由于您已经在使用 Boost,因此您可以在 Boost 中找到几个这样的工具。
【讨论】:
以上是关于glibc 应用程序一直占用未使用的内存,直到退出前的主要内容,如果未能解决你的问题,请参考以下文章
网络未准备好或其他程序占用控制端口,请退出程序检查IP配置后再试!!