简体   繁体   English

Linux 分配器不释放小块内存

[英]Linux Allocator Does Not Release Small Chunks of Memory

The Linux glibc allocator seems to be behaving weirdly. Linux glibc 分配器似乎表现得很奇怪。 Hopefully, someone can shed some light on this.希望有人可以对此有所了解。 Here is the source file that I have:这是我拥有的源文件:

first.cpp:第一个.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:第二个.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;
}

I compile both:我编译两者:

$ g++ -o first first.cpp
$ g++ -o second second.cpp

I run first, and after it's sleeping, I see the resident memory size:我先运行,在它休眠后,我看到常驻内存大小:

When I compile first.cpp, and run it, I look at memory with ps:当我编译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

Notice the resident memory size.注意常驻内存大小。 In first, the resident memory size is 53016k.首先,常驻内存大小为 53016k。 in second, it is 1024k.其次,它是 1024k。 First never released the allocations back to the kernel for some reason or another.首先,出于某种原因,从未将分配释放回内核。

Why does the first program not relinquish memory to the kernel, but the second program does?为什么第一个程序不将内存交给内核,而第二个程序却可以? I understand that the first program uses a linked list and the linked list probably allocates some nodes on the same page as the data we're freeing.我知道第一个程序使用链表,链表可能在与我们释放的数据相同的页面上分配一些节点。 However, those nodes should be freed, as we're popping those nodes off, then clearing the linked list.然而,这些节点应该被释放,因为我们正在弹出这些节点,然后清除链表。 If you run either of these programs through valgrind, it comes back with no memory leaks.如果您通过 valgrind 运行这些程序中的任何一个,它就会返回而不会出现内存泄漏。 What is probably happening is memory gets fragmented in first.cpp that doesn't in second.cpp.可能发生的事情是内存在 first.cpp 中碎片化,而在 second.cpp 中没有。 However, if all memory on a page is freed, how does that page not get relinquished back to the kernel?然而,如果一个页面上的所有内存都被释放了,那该页面如何不被释放回内核呢? What does it take for memory to get relinquished back to the kernel?将内存交还给内核需要什么? How can I modify first.cpp (continuing to put the char*'s in a list) so that the memory is relinquished to the kernel.如何修改 first.cpp(继续将 char* 放入列表中),以便将内存交给内核。

This behaviour is intentional, there is a tunable threshold that glibc uses to decide whether to actually return memory to the system or whether to cache it for later reuse.这种行为是有意为之,glibc 使用一个可调阈值来决定是将内存实际返回给系统还是缓存它以供以后重用。 In your first program you make lots of small allocations with each push_back and those small allocations are not a contiguous block and are presumably below the threshold, so don't get returned to the OS.在您的第一个程序中,您对每个push_back进行了大量小分配,这些小分配不是连续块,并且可能低于阈值,因此不要返回给操作系统。

Calling malloc_trim(0) after clearing the list should cause glibc to immediately return the top-most region of free memory to the system (requiring a sbrk system call next time memory is needed.)在清除列表后调用malloc_trim(0)应该会导致 glibc 立即将空闲内存的最顶部区域返回给系统(下次需要内存时需要sbrk系统调用。)

If you really need to override the default behaviour (which I wouldn't recommend unless profiling reveals it actually helps) then you should probably use strace and/or experiment with mallinfo to see what's actually happening in your program, and maybe using mallopt to adjust the threshold for returning memory to the system.如果您确实需要覆盖默认行为(除非分析表明它确实有帮助,否则我不建议这样做),那么您可能应该使用 strace 和/或使用mallinfo进行试验以查看程序中实际发生的情况,并且可能使用mallopt进行调整将内存返回给系统的阈值。

It keeps the smaller chunks available in case you request them again.它使较小的块可用,以防您再次请求它们。 It is a simple caching optimization, and not behaviour to be concerned about.这是一个简单的缓存优化,而不是需要关注的行为。

Typically, the memory allocated by new will only be returned to the system when the process terminates.通常, new分配的内存只有在进程终止时才会返回给系统。 In the second case, I suspect that libc is using a special allocator for very large continuous blocks, which does return it, but I'd be very surprised if any of your new char[1024] were returned, and on many Unices, even the large block won't be returned.在第二种情况下,我怀疑libc对非常大的连续块使用了一个特殊的分配器,它确实返回了它,但是如果你的任何new char[1024]被返回,我会非常惊讶,并且在许多 Unices 上,甚至大块将不会返回。

(Editing down my answer, since there really isn't any issue here.) (编辑我的答案,因为这里真的没有任何问题。)

As has been noted, there isn't really an issue here.如前所述,这里没有真正的问题。 Johnathon Wakely hits the nail on the head.乔纳森·韦克利一针见血。

When the memory utilization is not what I expect it to be on Linux, I usually start my analysis using the mtrace tool, and analyzing the /proc/self/maps file.当内存利用率不是我在 Linux 上预期的那样时,我通常使用mtrace工具开始我的分析,并分析/proc/self/maps文件。

mtrace is used by bracketing your code around two calls, one to starts the trace, and one that ends it. mtrace用于将您的代码mtrace在两个调用中,一个用于开始跟踪,另一个用于结束跟踪。

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

The mtrace calls are only active if the MALLOC_TRACE environment variable is set. mtrace调用仅在设置了MALLOC_TRACE环境变量时才处于活动状态。 It specifies the name of the file for the mtrace logging output.它指定了 mtrace 日志输出的文件名。 This logging output can then be analyzed for memory leaks.然后可以分析此日志记录输出是否存在内存泄漏。 A command line program called mtrace can be used to analyze the output.可以使用名为mtrace命令行程序来分析输出。

$ MALLOC_TRACE=mtrace.log ./a.out
$ mtrace ./a.out mtrace.log

The /proc/self/maps file provides a list of memory mapped regions in use by the current program, including anonymous regions. /proc/self/maps文件提供了当前程序正在使用的内存映射区域列表,包括匿名区域。 It can help identify regions which are particularly large, and then additional sleuthing is needed to determine what that region is associated with.它可以帮助识别特别大的区域,然后需要额外的侦查来确定该区域与什么相关联。 Below is a simple program to dump the /proc/self/maps file to another file.下面是一个将/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();
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM