下面的代码在我的 Debian 机器上引发了一个奇怪的内存行为。 即使清除了 maps,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 后,两个 map 都会被清除。该程序仍然运行并再次等待按下 ENTER,然后退出。
事实如下:
在我的 64 位 Debian 3.2.54-2 上,按下 ENTER 后(因此在清除映射后),当程序以 1 作为第一个参数启动时(因此在第二个映射中插入), htop 表示该程序仍然使用 500MB 的内存!!如果程序以 0 作为第一个参数启动,则内存被正确释放。
native 使用g++4.7.2和libstdc++.so.6.0.17。我试过 g++4.8.2 和 libstdc++.so.6.0.18,同样的问题。
- 我在 64 位 Fedora 21 上试过 g++4.9.2 和 libstdc++.so.6.0.20,同样的问题。
- 我已经在带有 g++4.8.2 和 libstdc++.so.6.0.19 的 32 位 Ubuntu 14.04 上试过,问题没有出现!
- 我试过使用 g++4.7.2 和 libstdc++.so.6.0.17 的 32 位 Debian 3.2.54-2,问题没有出现!
- 我在 64 位 Windows 上试过,问题没有出现!
- 在存在问题的机器上,如果您反转清除映射的行(因此,如果您先清除 uint64_t、size_t 映射,问题就会消失!
有人对这一切有解释吗???
最佳答案
我建议寻找 here其次是 here .基本上,libc malloc 首先使用 mmap 进行“大”分配(> 128k),并使用 brk/freelists 进行小分配。一旦大分配中的一个被释放,它就会尝试调整可能使用 malloc 的大小,但前提是大小小于最大值(在第一个链接中定义)。在 32 位的情况下,您的字符串远高于最大值,因此它继续对大型分配使用 mmap/munmap,并且仅将较小的映射节点分配放入使用 sbrk 从系统检索的内存中。这就是您在 32 位系统上看不到“问题”的原因。
另一位是碎片之一,当 free 尝试合并内存并将其返回给系统时。默认情况下,free 只会将小块粘贴到空闲列表中,以便它们为下一个请求做好准备。如果堆顶部有足够大的 block 空闲,它将尝试将内存返回给系统 see comment here .阈值为64K。
在传递 1
的情况下,您的分配模式可能会在堆顶部附近留下 counterToSize
映射的某些元素,从而阻止它在最后一次被释放释放其中一根弦。 counterToSize
映射中各种对象的释放量不足以触发阈值。
如果您切换 .clear()
调用的顺序,您会发现内存已按预期释放。此外,如果您要分配一大块内存并在清除后立即释放它,它会触发释放。 (在这种情况下,大只需要超过 128 字节 - 用于触发快速垃圾箱的最大大小。(即,该大小的空闲和小于该大小的分配只是进入列表。
我希望这很清楚。基本上,这不是真正的问题。你已经映射了一些页面。您没有在它们上使用任何东西,但可能释放它们的最后一个免费版本太小而无法触发该代码路径。下次您尝试分配某些东西时,它将从您已有的内存中提取(您可以在不增加内存的情况下再次执行整个循环)。
哦,如果此时确实需要返回页面,您可以手动调用 malloc_trim()
并强制它进行合并/清理。
关于linux - std 映射和 shared_ptr 的奇怪内存行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27446337/