linux - std 映射和 shared_ptr 的奇怪内存行为

标签 linux c++11 memory-leaks 64-bit gcc4.7

下面的代码在我的 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/

相关文章:

linux - Bower 输出为 JSON 格式

c++ - 我可以编写什么程序来卡住/挂起我的程序来测试看门狗定时器?

c++ - 如何检查 union 是否包含类型(使用 type_traits)?

c++11/14 make_unique std::string 的模糊重载

Android内存泄露——致命的拥抱

.net - .Net应用程序中极端私有(private)字节使用分析, native 内存泄漏?

python - 以相反的顺序批量重命名目录

linux - Linux 中的 NASM 代码出现段错误

c++ - 是否可以使用 operator new 和 initialiser 语法初始化非 POD 数组?

android - Picasso 和 FragmentStatePagerAdapter 的内存泄漏