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

Linux Allocator Does Not Release Small Chunks of Memory

本文关键字:内存 释放 分配器 Linux      更新时间:2023-10-16

Linux glibc 分配器似乎行为怪异。希望有人能对此有所了解。这是我拥有的源文件:

第一.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;
}

第二.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 首先.cpp$ g++ -o 秒.cpp

先跑,在它休眠后,我看到常驻内存大小:

当我首先编译.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 运行这些程序中的任何一个,它就会返回而不会发生内存泄漏。可能正在发生的事情是内存首先被碎片化.cpp而第二.cpp则不会。但是,如果页面上的所有内存都被释放,该页面如何不被放弃回内核?内存被放弃回内核需要什么?我该如何先修改.cpp(继续将 char* 放在列表中)以便将内存交给内核。

这种行为是有意为之的,glibc 使用一个可调阈值来决定是实际将内存返回系统还是缓存内存以供以后重用。在你的第一个程序中,你对每个push_back进行大量的小分配,这些小的分配不是一个连续的块,可能低于阈值,所以不要返回到操作系统。

清除列表后调用malloc_trim(0)应该会导致 glibc 立即将可用内存的最顶层区域返回到系统(下次内存需要sbrk系统调用是必需的。

如果您确实需要覆盖默认行为(除非分析显示它确实有帮助,否则我不建议这样做),那么您可能应该使用 strace 和/或尝试 mallinfo至查看程序中实际发生的情况,并可能使用 mallopt调整将内存返回到系统的阈值。

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

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

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

如前所述,这里并没有真正的问题。Johnathon Wakely一针见血。

当内存利用率不是我在 Linux 上的预期时,我通常使用 mtrace 工具开始分析,并分析/proc/self/maps文件。

mtrace 通过将代码括在两个调用周围来使用,一个用于启动跟踪,另一个用于结束跟踪。

  mtrace();
  {
      // do stuff
  }
  muntrace();

仅当设置了MALLOC_TRACE环境变量时,mtrace调用才处于活动状态。它指定 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();
}