下面的代码在我的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,同样的问题。
有人对此有一个解释吗?
我建议先看这里,然后再看这里。基本上,libc malloc首先使用mmap进行“大”分配(> 128k),使用brk / freelists进行小分配。一旦释放了一个较大的分配,它就会尝试在可能使用malloc的位置调整大小,但前提是该大小小于最大值(在第一个链接中定义)。在32位的情况下,您的字符串远高于最大值,因此它继续对大型分配使用mmap / munmap,并且仅将较小的映射节点分配放入使用sbrk从系统检索的内存中。这就是为什么您在32位系统上看不到“问题”的原因。
另一位是碎片之一,空闲时尝试合并内存并将其返回给系统。默认情况下,free会将小块粘贴到空闲列表上,以便它们准备下一个请求。如果在堆的顶部释放了足够大的块,它将尝试将内存返回给系统,请参见此处的注释。阈值为64K。
如果您通过了分配模式,则分配模式1
可能会使counterToSize
映射的某些元素靠近堆的顶部,从而阻止该值在字符串的最后一次释放时被释放。counterToSize
地图内部各种对象的释放量不足以触发阈值。
如果切换.clear()
呼叫顺序,您会发现内存已按预期释放。另外,如果您要分配大量内存并在清除后立即释放它,则会触发释放。(在这种情况下,大只需要超过128个字节-用于触发快速垃圾箱的最大大小。(即,具有该大小的free且分配的空间小于该大小的free进入列表。
我希望那是清楚的。基本上,这不是一个真正的问题。您已经映射了一些页面。您没有在它们上使用任何东西,但是可能释放它们的最后一个免费软件太小,无法触发该代码路径。下次尝试分配某些内容时,它将从您已有的内存中提取(您可以在不增加内存的情况下再次执行整个循环)。
哦,malloc_trim()
如果您当时确实需要返回页面,则可以手动调用并强制它进行合并/清除。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句