下面的代码在我的Debian机器上引发了奇怪的内存行为。 即使在清除了地图之后,htop也显示该程序仍然使用大量内存,这让我觉得存在内存泄漏。奇怪的是,它只在某些情况下出现。
#include <map>
#include <iostream>
#include <memory>
int main(int argc, char** argv)
{
if (argc != 2)
{
std::cout << "Usage: " << argv[0] << " <1|0> " << std::endl;
std::cout << "1 to insert in the second map and see the problem "
"and 0 to not insert" << std::endl;
return 0;
}
bool insertion = atoi(argv[1]);
std::map<uint64_t, std::shared_ptr<std::string> > mapStd;
std::map<uint64_t, size_t> counterToSize;
size_t dataSize = 1024*1024;
uint64_t counter = 0;
while(counter < 10000)
{
std::shared_ptr<std::string> stringPtr =
std::make_shared<std::string>(dataSize, 'a');
mapStd[counter] = stringPtr;
if (insertion)
{
counterToSize[counter] = dataSize;
}
if (counter > 500)
{
mapStd.erase(mapStd.begin());
}
std::cout << "\rInserted chunk " << counter << std::flush;
counter++;
}
std::cout << std::endl << "Press ENTER to delete the maps" << std::endl;
char a;
std::cin.get(a); // wait for ENTER to be pressed
mapStd.clear(); // clear both maps
counterToSize.clear();
std::cout << "Press ENTER to exit the program" << std::endl;
std::cin.get(a); // wait for ENTER to be pressed
return 0;
}
解释
代码在堆栈上创建两个映射(但如果它们是在堆上创建的,则问题是相同的)。然后它将字符串的std :: shared_ptr插入到第一个映射中。每个字符串的大小为1MB。一旦插入了500个字符串,就会删除每个新插入的第一个字符串,这样地图使用的总内存总是等于500MB。当插入总共10000个字符串时,程序等待用户按ENTER。如果启动程序并将1作为第一个参数传递,则对于第一个映射中的每个插入,还会对第二个映射进行另一次插入。如果第一个参数为0,则不使用第二个参数。首次按下ENTER后,两个地图都将被清除。程序仍然运行并再次等待按下ENTER,然后退出。
以下是事实:
在我的64位Debian 3.2.54-2上,按下ENTER后(因此清除了映射后),以及以1作为第一个参数启动程序时(因此插入第二个映射) ),htop表示该程序仍然使用500MB的内存!!如果以0作为第一个参数启动程序,则正确释放内存。
本机使用g ++ 4.7.2和libstdc ++。so.6.0.17。我试过用g ++ 4.8.2和libstdc ++。so.6.0.18,同样的问题。
有人对这一切有解释吗?
答案 0 :(得分:3)
我建议查看here后跟here。基本上,libc malloc开始使用mmap for&#34; large&#34;分配(> 128k)和小分配的brk / freelists。一旦其中一个大分配是免费的,它就会尝试调整可能使用malloc的大小,但前提是大小小于max(在第一个链接上定义)。在32位的情况下,你的字符串远远超过最大值,因此它继续使用mmap / munmap进行大量分配,并且只将较小的映射节点分配放入使用sbrk从系统检索的内存中。这就是为什么你没有看到&#39;问题&#39;在32位系统上。
另一位是碎片之一,当空闲时尝试合并内存并将其返回给系统。默认情况下,free会将小块粘贴到空闲列表中,以便他们准备好进行下一个请求。如果堆顶部有足够大的块空闲,它将尝试将内存返回给系统see comment here。阈值为64K。
如果您传递1
,您的分配模式可能会使counterToSize
地图的某些元素靠近堆顶部,从而阻止它在上一个版本的最后一个版本中被释放字符串。 counterToSize
地图内部各种对象的释放量不足以触发阈值。
如果您切换.clear()
来电的顺序,您会发现内存已经发布,就像您期望的那样。此外,如果你要分配一大块内存并在清除后立即立即释放它,它就会触发释放。 (在这种情况下,大的数字只需要超过128个字节 - 用于触发快速容器的最大大小。(即那个大小的自由,并且分配的数量小于该大小,只需进入列表。
我希望这很清楚。基本上,这不是一个真正的问题。您已经映射了一些页面。你没有在它们上面使用任何东西,但可能释放它们的最后一个免费版本太小而无法触发该代码路径。下次你尝试分配一些东西时,它会从你已经拥有的内存中拉出来(你可以再次完成整个循环而不增加内存)。
哦,你可以手动调用malloc_trim()
并强制它进行合并/清理,如果你真的需要那些页面。