Linux glibc 的 ma…

最近使用ACE的Message_Block時發現,程序運行一段時間之後內存越喫越多,即便沒有請求,內存也不會下降。

在使用 valgrind 排除內存泄漏之後,把懷疑的對象轉到了Message_Block上。

用ACE的測試用例改了一個測試程序:

#include "ace/Log_Msg.h"
#include "ace/Message_Block.h"
#include
#include
#include
#include


using namespace std;

#define MY_DEBUG(FMT, ...)  \
        ACE_DEBUG((LM_DEBUG, FMT, __VA_ARGS__))
void foo (void);


void create_packet(ACE_Message_Block** packet, int cnt, int packet_size)
{
  for (int i = 0; i < cnt; i++) {
    packet[i] = new ACE_Message_Block(packet_size);
  }

  cout << "Create " << cnt << "packets" << endl;
}

void delete_packet(ACE_Message_Block** packet, int cnt)
{
  for (int i = 0; i < cnt; i++) {
    delete packet[i];
  }

  cout << "Delete " << cnt << "packets" << endl;
}

void hang()
{
  while (true) {
    ACE_OS::sleep(1);
  }
}


int ACE_TMAIN (int, ACE_TCHAR *[])
{
  int cnt;
  int packet_size;

  cout<< "packet size = ";
  cin>>packet_size;
  cout<< "\n packet number = ";
  cin>>cnt;

  ACE_Message_Block **packet = new ACE_Message_Block*[cnt];
  create_packet(packet, cnt, packet_size);
  delete_packet(packet, cnt);
  malloc_trim(0);
  create_packet(packet, cnt, packet_size);
  delete_packet(packet, cnt);
  malloc_trim(0);
  create_packet(packet, cnt, packet_size);
  delete_packet(packet, cnt);
  hang();
  return 0;
}

測試時,每個block 1k,測了10萬個block,結果運行到hang時,該程序的內存一直維持在最高位。

上網google了一下,發現是linux平臺下 mallopt 的內存管理機制導致的。

當程序對malloc出來的內存執行 free時,它只是標識這塊內存被釋放了。

       void free(void *ptr) 
    {
            struct mem_control_block *free;
            free = ptr - sizeof(struct mem_control_block);
            free->is_available = 1;
            return;
    }

至於是不是真的把這塊內存返還內核就不一定了。
通過man mallopt 可以得知: 如果被free的內存太小了 (小於M_TRIM_THRESHOLD),那麼glibc 出於性能的考慮會把這塊內存保留在當前進程堆中,以滿足該進程之後malloc的需求。

所以當進程從堆中申請了海量小內存時,就會出現該程序的內存始終都是隻增不減。

解決辦法:
1. 使用內存池,程序改動較大,但對程序性能和管理都是很有益
2. 顯示調用 malloc_trim(0) 來強制回收被釋放的堆內存。
3. 調小M_TRIM_THRESHOLD ()

這裏使用的方法2,上例的程序修改如下:

#include "ace/Log_Msg.h"
#include "ace/Message_Block.h"
#include
#include
#include
#include
#include


using namespace std;

#define MY_DEBUG(FMT, ...)  \
        ACE_DEBUG((LM_DEBUG, FMT, __VA_ARGS__))
void foo (void);


void create_packet(ACE_Message_Block** packet, int cnt, int packet_size)
{
  for (int i = 0; i < cnt; i++) {
    packet[i] = new ACE_Message_Block(packet_size);
  }

  cout << "Create " << cnt << "packets" << endl;
}

void delete_packet(ACE_Message_Block** packet, int cnt)
{
  for (int i = 0; i < cnt; i++) {
    delete packet[i];
  }

  cout << "Delete " << cnt << "packets" << endl;
}
void hang()
{
  while (true) {
    ACE_OS::sleep(1);
  }
}

class worker : public ACE_Task
{
public:
  int svc()
  {
    ACE_OS::sleep(5);
    malloc_trim(0);
    return 0;
  }
}
;


int ACE_TMAIN (int, ACE_TCHAR *[])
{
  int cnt;
  int packet_size;

  cout<< "packet size = ";
  cin>>packet_size;
  cout<< "\n packet number = ";
  cin>>cnt;

//  mallopt(M_TRIM_THRESHOLD, 10240);
  ACE_Message_Block **packet = new ACE_Message_Block*[cnt];
  create_packet(packet, cnt, packet_size);
  delete_packet(packet, cnt);
  malloc_trim(0);
  create_packet(packet, cnt, packet_size);
  delete_packet(packet, cnt);
  malloc_trim(0);
  create_packet(packet, cnt, packet_size);
delete_packet(packet, cnt);
 worker *w = new worker();
  w->activate();
  hang()
;

  ACE_TRACE("main");

  ACE_DEBUG ((LM_INFO, ACE_TEXT ("%IHi Mom\n")));
  foo();
  ACE_DEBUG ((LM_INFO, ACE_TEXT ("%IGoodnight\n")));

  return 0;
}

由於alloc_trim操作過多會影響性能,所以建議在實際程序中另啓一個後臺線程,週期性地執行。
上面的實例也另起一個線程主要是爲了驗證另起的線程調用malloc_trim也能回收其他線程的空閒內存。

注1: mem_control_block 是從堆中分配的內存的描述信息
   struct mem_control_block {
    int is_available;    //這是一個標記?
    int size;            //這是實際空間的大小
    };


參考:
http://www.cnblogs.com/lookof/archive/2013/03/26/2981768.html
http://www.bccn.net/Article/kfyy/cyy/jszl/200608/4238.html
man mallopt
http://stackoverflow.com/questions/10943907/linux-allocator-does-not-release-small-chunks-of-memory


轉載請註明轉自高孝鑫的博客
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章