Linux 分配器不会释放小块内存
Posted
技术标签:
【中文标题】Linux 分配器不会释放小块内存【英文标题】:Linux Allocator Does Not Release Small Chunks of Memory 【发布时间】:2012-06-12 05:18:54 【问题描述】:Linux glibc 分配器的行为似乎很奇怪。希望有人可以对此有所了解。这是我拥有的源文件:
first.cpp:
#include <unistd.h>
#include <stdlib.h>
#include <list>
#include <vector>
int main()
std::list<char*> ptrs;
for(size_t i = 0; i < 50000; ++i)
ptrs.push_back( new char[1024] );
for(size_t i = 0; i < 50000; ++i)
delete[] ptrs.back();
ptrs.pop_back();
ptrs.clear();
sleep(100);
return 0;
second.cpp:
#include <unistd.h>
#include <stdlib.h>
#include <list>
int main()
char** ptrs = new char*[50000];
for(size_t i = 0; i < 50000; ++i)
ptrs[i] = new char[1024];
for(size_t i = 0; i < 50000; ++i)
delete[] ptrs[i];
delete[] ptrs;
sleep(100);
return 0;
我都编译了:
$ g++ -o first first.cpp $ g++ -o 秒 second.cpp我先运行,在它休眠后,我看到常驻内存大小:
当我编译 first.cpp 并运行它时,我用 ps 查看内存:
$ ./first&
$ ps aux | grep first
davidw 9393 1.3 0.3 64344 53016 pts/4 S 23:37 0:00 ./first
$ ./second&
$ ps aux | grep second
davidw 9404 1.0 0.0 12068 1024 pts/4 S 23:38 0:00 ./second
注意常驻内存大小。首先,常驻内存大小为 53016k。其次是1024k。首先从未出于某种原因将分配释放回内核。
为什么第一个程序不将内存释放给内核,而第二个程序却可以?我知道第一个程序使用链表,链表可能会在同一页面上分配一些节点作为我们正在释放的数据。但是,这些节点应该被释放,因为我们要弹出这些节点,然后清除链表。如果你通过 valgrind 运行这些程序中的任何一个,它就不会出现内存泄漏。可能发生的情况是内存在 first.cpp 中变得碎片化,而在 second.cpp 中没有。但是,如果一个页面上的所有内存都被释放,那么该页面如何不被释放回内核呢?将内存交还给内核需要什么?如何修改 first.cpp(继续将 char* 放入列表中)以便将内存让给内核。
【问题讨论】:
使用收缩来适应,描述here。在这种情况下,请执行std::list<char*>().swap(ptrs)
。
恐怕这里还有其他问题...这是我的新程序: int main() std::listmalloc
/free
对的文章,其中没有实际使用结果的代码,同样的逻辑也适用于 new
/delete
对。
@hvd 不应该,至少在没有完整程序分析的情况下不应该。对 operator new
和 operator delete
的调用是 C++ 中的可观察行为。
【参考方案1】:
通常情况下,new
分配的内存只会在进程终止时返回给系统。在第二种情况下,我怀疑libc
正在使用一个特殊的分配器来处理非常大的连续块,这确实会返回它,但如果你的任何new char[1024]
被返回,我会感到非常惊讶,而且在许多 Unices 上,甚至大块不会被退回。
【讨论】:
【参考方案2】:如果您再次请求它们,它会保留较小的块可用。这是一个简单的缓存优化,而不是需要关注的行为。
【讨论】:
【参考方案3】:这种行为是有意的,glibc 使用一个可调阈值来决定是否将内存实际返回给系统,或者是否缓存它以供以后重用。在您的第一个程序中,您对每个 push_back
进行了大量小分配,这些小分配不是连续的块,并且可能低于阈值,因此不要返回给操作系统。
在清除列表后调用 malloc_trim(0)
应该会导致 glibc 立即
将空闲内存的最顶层区域返回给系统(下次内存时需要 sbrk
系统调用
需要。)
如果您确实需要覆盖默认行为(除非分析表明它确实有帮助,否则我不建议您这样做),那么您可能应该使用 strace 和/或尝试
mallinfo
到
看看你的程序中实际发生了什么,也许使用mallopt
调整内存返回系统的阈值。
【讨论】:
关于 malloc_trim:从 glibc 2.8 开始,此函数释放所有领域和所有具有整个空闲页面的块中的内存。在 glibc 2.8 之前,此函数仅释放主竞技场中堆顶部的内存。(参考:man7.org/linux/man-pages/man3/malloc_trim.3.html)【参考方案4】:(编辑我的答案,因为这里真的没有任何问题。)
如前所述,这里没有真正的问题。 Johnathon Wakely 一针见血。
当内存利用率在 Linux 上不是我期望的那样时,我通常使用mtrace
工具开始我的分析,并分析/proc/self/maps
文件。
mtrace
用于将代码括在两个调用中,一个用于启动跟踪,另一个用于结束跟踪。
mtrace();
// do stuff
muntrace();
mtrace
调用仅在设置了MALLOC_TRACE
环境变量时才有效。它指定 mtrace 日志输出的文件名。然后可以分析此日志记录输出是否存在内存泄漏。可以使用名为mtrace
的命令行程序来分析输出。
$ MALLOC_TRACE=mtrace.log ./a.out
$ mtrace ./a.out mtrace.log
/proc/self/maps
文件提供了当前程序正在使用的内存映射区域的列表,包括匿名区域。它可以帮助识别特别大的区域,然后需要额外的侦查来确定该区域与什么相关联。下面是一个将/proc/self/maps
文件转储到另一个文件的简单程序。
void dump_maps (const char *outfilename)
std::ifstream inmaps("/proc/self/maps");
std::ofstream outf(outfilename, std::ios::out|std::ios::trunc);
outf << inmaps.rdbuf();
【讨论】:
以上是关于Linux 分配器不会释放小块内存的主要内容,如果未能解决你的问题,请参考以下文章