簡體   English   中英

Linux 分配器不釋放小塊內存

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

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 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。 首先,出於某種原因,從未將分配釋放回內核。

為什么第一個程序不將內存交給內核,而第二個程序卻可以? 我知道第一個程序使用鏈表,鏈表可能在與我們釋放的數據相同的頁面上分配一些節點。 然而,這些節點應該被釋放,因為我們正在彈出這些節點,然后清除鏈表。 如果您通過 valgrind 運行這些程序中的任何一個,它就會返回而不會出現內存泄漏。 可能發生的事情是內存在 first.cpp 中碎片化,而在 second.cpp 中沒有。 然而,如果一個頁面上的所有內存都被釋放了,那該頁面如何不被釋放回內核呢? 將內存交還給內核需要什么? 如何修改 first.cpp(繼續將 char* 放入列表中),以便將內存交給內核。

這種行為是有意為之,glibc 使用一個可調閾值來決定是將內存實際返回給系統還是緩存它以供以后重用。 在您的第一個程序中,您對每個push_back進行了大量小分配,這些小分配不是連續塊,並且可能低於閾值,因此不要返回給操作系統。

在清除列表后調用malloc_trim(0)應該會導致 glibc 立即將空閑內存的最頂部區域返回給系統(下次需要內存時需要sbrk系統調用。)

如果您確實需要覆蓋默認行為(除非分析表明它確實有幫助,否則我不建議這樣做),那么您可能應該使用 strace 和/或使用mallinfo進行試驗以查看程序中實際發生的情況,並且可能使用mallopt進行調整將內存返回給系統的閾值。

它使較小的塊可用,以防您再次請求它們。 這是一個簡單的緩存優化,而不是需要關注的行為。

通常, new分配的內存只有在進程終止時才會返回給系統。 在第二種情況下,我懷疑libc對非常大的連續塊使用了一個特殊的分配器,它確實返回了它,但是如果你的任何new char[1024]被返回,我會非常驚訝,並且在許多 Unices 上,甚至大塊將不會返回。

(編輯我的答案,因為這里真的沒有任何問題。)

如前所述,這里沒有真正的問題。 喬納森·韋克利一針見血。

當內存利用率不是我在 Linux 上預期的那樣時,我通常使用mtrace工具開始我的分析,並分析/proc/self/maps文件。

mtrace用於將您的代碼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();
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM