Linux分配器不会释放小块内存

时间:2012-06-08 06:18:10

标签: c++ memory heap allocation

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 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。由于某种原因,First从未将分配释放回内核。

为什么第一个程序不会将内存放弃到内核,但第二个程序呢?据我所知,第一个程序使用链表,链表可能会在我们释放的数据中分配同一页面上的一些节点。但是,应该释放这些节点,因为我们将关闭这些节点,然后清除链接列表。如果你通过valgrind运行这些程序中的任何一个,它会返回没有内存泄漏。可能发生的是在first.cpp中的内存碎片,而不是在second.cpp中。但是,如果页面上的所有内存都被释放,那么该页面如何不被放弃回内核?将内存放回内核需要什么?如何修改first.cpp(继续将char *放在列表中),以便将内存放到内核中。

4 个答案:

答案 0 :(得分:16)

此行为是故意的,glibc使用一个可调阈值来决定是否实际将内存返回给系统,或者是否将其缓存以供以后重用。在您的第一个程序中,您为每个push_back进行了大量的小分配,并且这些小分配不是连续的块,可能低于阈值,因此不要返回到操作系统。

清除列表后调用malloc_trim(0)会立即导致glibc 将最上面的可用内存区域返回给系统(下次需要sbrk系统调用内存 需要。)

如果你真的需要覆盖默认行为(我不建议除非分析显示它实际上有帮助)那么你应该使用strace和/或实验 mallinfo来 看看程序中实际发生了什么,并且可能使用mallopt 调整将内存返回系统的阈值。

答案 1 :(得分:5)

它会保留较小的块,以备再次请求时使用。这是一个简单的缓存优化,而不是需要关注的行为。

答案 2 :(得分:3)

通常,new分配的内存只会在进程终止时返回给系统。在第二种情况下,我怀疑libc正在为非常大的连续块使用特殊的分配器,它会返回它,但如果你的任何new char[1024]被返回,我会非常惊讶,并且许多Unices,即使是大块也不会被退回。

答案 3 :(得分:2)

(编辑我的答案,因为这里确实没有任何问题。)

正如已经指出的那样,这里确实没有问题。 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();
}