我有一个在 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()
,然后内存才一次性恢复到操作系统。
任何人都可以提供有关如何继续调试的更多提示吗?为什么我的程序不归还该内存?
最佳答案
这里的一切都基于在 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?)。
我找到了 this glibc 的 bugzilla 上的旧 (2006) 票。它在那里说 free
永远不会将内存返回给内核,除非调用 malloc_trim
。
较新版本的 free
似乎具有执行内部 systrim
函数的代码,该函数应该修剪 arena 的顶部,但我无法使其工作。
关于c++ - glibc 应用程序保留未使用的内存直到退出,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48651432/