理解glibc中关于malloc的实现策略

背景介绍

由于业务需求,需要将应用程序的内存占用降下来。我们是采用pss这个指标来衡量内存占用的,这个指标衡量应用程序大约占用多少物理内存。经过一段时间测试观察,pss占用不符合正常逻辑,表现在以下几个方面。

  • 空闲状态下,pss占用缓慢增长,增长到一个上限值就不增长了,并且长时间没有下降。
  • 使用heaptrack工具观察堆内存占用情况,未发现内存泄漏,并且heaptrack显示的堆占用与pss显示的占用严重不匹配。

linux关于pss的介绍
The "proportional set size" (PSS) of a process is the count of pages it has in memory, where each page is divided by the number of processes sharing it. So if a process has 1000 pages all to itself, and 1000 shared with one other process, its PSS will be 1500

初步排查

起初,怀疑是程序内部导致的内存泄露,原因是空闲状态pss一直缓慢增长。因此,将程序的部分逻辑抽取出来,单独写一个测试程序测试。该测试程序周期发起http请求,测试发现,pss的占用确实在缓慢增长,这不符合正确逻辑,使用到的内存都是用完就释放的,除非底层库缓存了一部分数据。

该测试程序实际上是将真正需要做的事情扔给线程池做的,因此我将线程池去掉,直接在主线程周期发起http请求,在这种情况下,pss确实不增长了。因此,我怀疑pss的增长与线程池有关系。我尝试修改线程池里面线程的个数,发现线程个数越多,pss增长的上限值越高。

经过以上尝试,基本可以确定是线程的原因导致pss无故增长的,但具体原因还是很迷糊。

重新编写测试程序

第一步

编写以下程序,输出变量a的地址,该地址为main函数的地址。

int main()
{
  char a;
  std::cout << "[enter main],[pid : " << getpid() << "]"
          << "[ main addr : " << (void*)&a << " ]" << std::endl;
  getchar();
  return 0;
}

编译以上程序:g++ -std=c++11 main.cpp
执行以上程序:strace -f -t -TT -e trace=brk,mmap,munmap ./a.out

strace是一款可以跟踪系统调用的工具,使用该工具的目的是要跟踪,brk、mmap、munmap的调用情况,这几个调用涉及到堆内存的分配和释放。

执行结果:

11:16:06 brk(NULL) = 0x1239000 <0.000092>
...(此处有一堆mmap的操作,跳过,不深入调查)
11:16:06 brk(NULL) = 0x1239000 <0.000028>
11:16:06 brk(0x126b000) = 0x126b000 <0.000031>
[enter main],[pid : 10621][ main addr : 0x7ffe009618e1 ]

执行: cat /proc/10621/maps

/proc是特殊的文件系统,可以跟踪进程内部虚拟内存的分布情况
00400000-00409000 r-xp 00000000 08:02 6298518 /home/wuzhuorui/git/test/a.out
00608000-00609000 r--p 00008000 08:02 6298518 /home/wuzhuorui/git/test/a.out
00609000-0060a000 rw-p 00009000 08:02 6298518 /home/wuzhuorui/git/test/a.out
01239000-0126b000 rw-p 00000000 00:00 0 [heap]
...
7fb885c1c000-7fb885c20000 rw-p 00000000 00:00 0
...
7fb8861b4000-7fb8861b8000 rw-p 00000000 00:00 0
...
7fb8865fc000-7fb8865fd000 rw-p 00000000 00:00 0
7ffe00942000-7ffe00964000 rw-p 00000000 00:00 0 [stack]
7ffe009cb000-7ffe009ce000 r--p 00000000 00:00 0 [vvar]
7ffe009ce000-7ffe009d0000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]

可以得出以下结论:

  • main函数进入时,会调用brk命令,申请分配 0x0126b000-0x01239000=204800 byte = 200kb的堆空间
  • char a,该变量是在栈上分配的。

第二步,主线程malloc申请127kb堆内存

int main()
{
  //第一步内容
  //...
  
  //第二步新增内容
  std::cout << "[before malloc 1024*127 byte in main]" << std::endl;
  char* addr1 = (char*)malloc(1024 * 127);
  std::cout << "[after malloc  1024*127 byte in main],[ addr1 :"
            << (void*)addr1 << "]" << std::endl;
  getchar();
  return 0;
}

执行以上程序:strace -f -t -TT -e trace=brk,mmap,munmap ./a.out

第一步内容
[enter main],[pid : 10621][ main addr : 0x7ffe009618e1 ]
第二步内容
[before malloc 1024127 byte in main]
11:39:03 brk(0x128c000) = 0x128c000 <0.000044>
[after malloc 1024
127 byte in main],[ addr1 :0x124b440]

执行: cat /proc/10621/maps

00400000-00409000 r-xp 00000000 08:02 6298518 /home/wuzhuorui/git/test/a.out
00608000-00609000 r--p 00008000 08:02 6298518 /home/wuzhuorui/git/test/a.out
00609000-0060a000 rw-p 00009000 08:02 6298518 /home/wuzhuorui/git/test/a.out
堆扩大了
01239000-0128c000 rw-p 00000000 00:00 0 [heap]
...
7fb885c1c000-7fb885c20000 rw-p 00000000 00:00 0
...
7fb8861b4000-7fb8861b8000 rw-p 00000000 00:00 0
...
7fb8863d1000-7fb8863d5000 rw-p 00000000 00:00 0
...
7fb8865b2000-7fb8865b8000 rw-p 00000000 00:00 0
...
7fb8865fc000-7fb8865fd000 rw-p 00000000 00:00 0
7ffe00942000-7ffe00964000 rw-p 00000000 00:00 0 [stack]
7ffe009cb000-7ffe009ce000 r--p 00000000 00:00 0 [vvar]
7ffe009ce000-7ffe009d0000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]

  • 可以看出malloc申请堆内存的操作,导致调用系统调用brk扩大了堆的大小,虽然实质上堆的空间是够用的。

第三步:主线程malloc申请256kb堆内存

int main()
{
  //第一步内容
  //...
  
  //第二步内容
  //...
  
  //第三步新增内容
  std::cout << "[before malloc 1024*256 byte in main]" << std::endl;
  char* addr2 = (char*)malloc(1024 * 256);
  std::cout << "[after malloc  1024*256 byte in main],[ addr2 :"
            << (void*)addr2 << "]" << std::endl;
  getchar();
  return 0;
}

执行以上程序:strace -f -t -TT -e trace=brk,mmap,munmap ./a.out

...
第三步新增内容
[before malloc 1024256 byte in main]
11:48:57 mmap(NULL, 266240, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb8865b9000 <0.000135>
[after malloc 1024
256 byte in main],[ addr2 :0x7fb8865b9010]

执行: cat /proc/10621/maps

00400000-00409000 r-xp 00000000 08:02 6298518 /home/wuzhuorui/git/test/a.out
00608000-00609000 r--p 00008000 08:02 6298518 /home/wuzhuorui/git/test/a.out
00609000-0060a000 rw-p 00009000 08:02 6298518 /home/wuzhuorui/git/test/a.out
01239000-0128c000 rw-p 00000000 00:00 0 [heap]
...
7fb885c1c000-7fb885c20000 rw-p 00000000 00:00 0
...
7fb8861b4000-7fb8861b8000 rw-p 00000000 00:00 0
...
7fb8863d1000-7fb8863d5000 rw-p 00000000 00:00 0
...
7fb8865b2000-7fb8865b8000 rw-p 00000000 00:00 0
7fb8865b9000-7fb8865fa000 rw-p 00000000 00:00 0
...
7fb8865fc000-7fb8865fd000 rw-p 00000000 00:00 0
7ffe00942000-7ffe00964000 rw-p 00000000 00:00 0 [stack]
7ffe009cb000-7ffe009ce000 r--p 00000000 00:00 0 [vvar]
7ffe009ce000-7ffe009d0000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]

  • 当malloc申请的堆内存过大时,可能会调用mmap申请新的虚拟地址空间。从红色位置可以看出虚拟地址空间确实新增了一块区域。

第四步:主线程free释放127kb堆内存

int main()
{
  //第一步内容
  //...
  
  //第二步内容
  //...
  
  //第三步内容
  //...
  
  //第四步新增内容
  std::cout << "[before free 1024*127 byte in main]" << std::endl;
  free(addr1);
  std::cout << "[after free 1024*127 byte in main]" << std::endl;
  getchar();
  return 0;
}

执行以上程序:strace -f -t -TT -e trace=brk,mmap,munmap ./a.out

...
新增内容
[before free 1024127 byte in main]
13:48:14 brk(0x126c000) = 0x126c000 <0.000144>
[after free 1024
127 byte in main]

执行: cat /proc/10621/maps

00400000-00409000 r-xp 00000000 08:02 6298518 /home/wuzhuorui/git/test/a.out
00608000-00609000 r--p 00008000 08:02 6298518 /home/wuzhuorui/git/test/a.out
00609000-0060a000 rw-p 00009000 08:02 6298518 /home/wuzhuorui/git/test/a.out
01239000-0126c000 rw-p 00000000 00:00 0 [heap]
...
7fb885c1c000-7fb885c20000 rw-p 00000000 00:00 0
...
7fb8861b4000-7fb8861b8000 rw-p 00000000 00:00 0
...
7fb8863d1000-7fb8863d5000 rw-p 00000000 00:00 0
...
7fb8865b2000-7fb8865b8000 rw-p 00000000 00:00 0
7fb8865b9000-7fb8865fa000 rw-p 00000000 00:00 0
...
7fb8865fc000-7fb8865fd000 rw-p 00000000 00:00 0
7ffe00942000-7ffe00964000 rw-p 00000000 00:00 0 [stack]
7ffe009cb000-7ffe009ce000 r--p 00000000 00:00 0 [vvar]
7ffe009ce000-7ffe009d0000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]

  • free操作触发brk系统调用,回收了一部份虚拟地址空间。主线程执行free操作可能可以使内存占用(pss)下降。

第五步:主线程free释放256kb

int main()
{
  //第一步内容
  //...
  
  //第二步内容
  //...
  
  //第三步内容
  //...
  
  //第四步内容
  //...
  
  //第五步新增内容
  std::cout << "[before free 1024*256 byte in main]" << std::endl;
  free(addr2);
  std::cout << "[after free 1024*256 byte in main]" << std::endl;
  getchar();
  return 0;
}

执行以上程序:strace -f -t -TT -e trace=brk,mmap,munmap ./a.out

...
新增内容
[before free 1024256 byte in main]
13:59:32 munmap(0x7fb8865b9000, 266240) = 0 <0.000112>
[after free 1024
256 byte in main]

执行: cat /proc/10621/maps

00400000-00409000 r-xp 00000000 08:02 6298518 /home/wuzhuorui/git/test/a.out
00608000-00609000 r--p 00008000 08:02 6298518 /home/wuzhuorui/git/test/a.out
00609000-0060a000 rw-p 00009000 08:02 6298518 /home/wuzhuorui/git/test/a.out
01239000-0126c000 rw-p 00000000 00:00 0 [heap]
...
7fb885c1c000-7fb885c20000 rw-p 00000000 00:00 0
...
7fb8861b4000-7fb8861b8000 rw-p 00000000 00:00 0
...
7fb8863d1000-7fb8863d5000 rw-p 00000000 00:00 0
...
7fb8865b2000-7fb8865b8000 rw-p 00000000 00:00 0
...
7fb8865fc000-7fb8865fd000 rw-p 00000000 00:00 0
7ffe00942000-7ffe00964000 rw-p 00000000 00:00 0 [stack]
7ffe009cb000-7ffe009ce000 r--p 00000000 00:00 0 [vvar]
7ffe009ce000-7ffe009d0000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]

  • 可以看出虚拟地址空间里已经没有了,7fb8865b9000-7fb8865fa000 rw-p 00000000 00:00 0 ,说明free操作是有效的。

第六步:子线程malloc申请127kb堆内存

void ThreadFun(std::string name, bool* is_ok, bool* is_exit)
{
    std::cout << "[before malloc 1024*127 byte in " 
              << name << "]" << std::endl;
    char* addr1 = (char*)malloc(1024 * 127);
    std::cout << "[after malloc  1024*127 byte in " << name
              << "],[ addr1 :" << (void*)addr1 << "]" << std::endl;
    getchar();
    *is_ok = true;
    while (!*is_exit)
        std::this_thread::sleep_for(std::chrono::milliseconds(100));  
}

int main()
{
  //第一步内容
  //...
  
  //第二步内容
  //...
  
  //第三步内容
  //...
  
  //第四步内容
  //...
  
  //第五步内容
  //...
  
  //第六步内容:启动子线程执行以上5步
  std::vector tpool;
  bool is_exit = false;
  for (int i = 0; i < 32; i++)
  {
      bool is_ok = false;
      tpool.emplace_back(ThreadFun, "thread" + std::to_string(i), &is_ok,&is_exit);
      while(!is_ok)
      std::this_thread::sleep_for(std::chrono::milliseconds(100));
  }
  is_exit = true;
  for (size_t i = 0; i < tpool.size(); i++) tpool[i].join();
  return 0;
}

执行以上程序:strace -f -t -TT -e trace=brk,mmap,munmap ./a.out

新增内容
1 14:09:07 mmap(NULL, 8392704, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7fb884d4c000 <0.000096>
strace: Process 24826 attached
2 [before malloc 1024127 byte in thread0]
3 [pid 24826] 14:09:07 mmap(NULL, 134217728, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = 0x7fb87cd4c000 <0.000042>
4 [pid 24826] 14:09:07 munmap(0x7fb87cd4c000, 53166080) = 0 <0.000047>
5 [pid 24826] 14:09:07 munmap(0x7fb884000000, 13942784) = 0 <0.000039>
6 [after malloc 1024
127 byte in thread0],[ addr1 :0x7fb8800008c0]

  • 第一行调用mmap为线程分配栈空间8M,线程栈上分配的变量用到的虚拟地址空间皆在此。
  • 第3~5行,在mmap申请了60M左右虚拟地址空间,malloc申请请求时执行,malloc申请返回前执行完毕。可见子线程上malloc分配内存的方式与主线程上malloc分配内存的方式不太一样。
  • 第6行,malloc申请内存返回的地址在第3~5行mmap映射返回的虚拟地址空间区间,可猜测malloc一次性向系统要了一大片虚拟内存,然后在此虚拟内存区间实现自己的分配策略。

00400000-00409000 r-xp 00000000 08:02 6298518 /home/wuzhuorui/git/test/a.out
00608000-00609000 r--p 00008000 08:02 6298518 /home/wuzhuorui/git/test/a.out
00609000-0060a000 rw-p 00009000 08:02 6298518 /home/wuzhuorui/git/test/a.out
01239000-0126c000 rw-p 00000000 00:00 0 [heap]
7fb880000000-7fb880041000 rw-p 00000000 00:00 0 (malloc用到此区间虚拟地址)
7fb880041000-7fb884000000 ---p 00000000 00:00 0
7fb884d4c000-7fb884d4d000 ---p 00000000 00:00 0
7fb884d4d000-7fb88554d000 rw-p 00000000 00:00 0
...
7fb885c1c000-7fb885c20000 rw-p 00000000 00:00 0
...
7fb8861b4000-7fb8861b8000 rw-p 00000000 00:00 0
...
7fb8863d1000-7fb8863d5000 rw-p 00000000 00:00 0
...
7fb8865b2000-7fb8865b8000 rw-p 00000000 00:00 0
...
7fb8865fc000-7fb8865fd000 rw-p 00000000 00:00 0
7ffe00942000-7ffe00964000 rw-p 00000000 00:00 0 [stack]
7ffe009cb000-7ffe009ce000 r--p 00000000 00:00 0 [vvar]
7ffe009ce000-7ffe009d0000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]

  • 以上红色和橙色部分是新申请的虚拟地址空间

第七步:子线程malloc申请256kb堆内存

void ThreadFun(std::string name, bool* is_ok, bool* is_exit)
{
    //第六步内容
    //...
    
    //第七步新增内容
    std::cout << "[before malloc 1024*256 byte in " 
              << name << "]" <<      std::endl;
    char* addr2 = (char*)malloc(1024 * 256);
    std::cout << "[after malloc  1024*256 byte in " << name
              << "],[ addr2 :" << (void*)addr2 << "]" << std::endl;
    getchar();
    *is_ok = true;
    while (!*is_exit)
        std::this_thread::sleep_for(std::chrono::milliseconds(100));  
}

int main()
{
  //第一步内容
  //...
  
  //第二步内容
  //...
  
  //第三步内容
  //...
  
  //第四步内容
  //...
  
  //第五步内容
  //...
  
  //第六步内容:启动子线程执行以上5步
  //...
  return 0;
}

执行以上程序:strace -f -t -TT -e trace=brk,mmap,munmap ./a.out

新增内容
[before malloc 1024256 byte in thread0]
[after malloc 1024
256 byte in thread0],[ addr2 :0x7fb8800204d0]

  • 可以看出malloc申请返回的地址,在第六步mmap申请的虚拟地址空间范围内,可以验证malloc向系统要了一大片虚拟地址空间,自己管理地址分配和缓存地址的猜想。

第八步:子线程释放127kb和256kb的堆内存

void ThreadFun(std::string name, bool* is_ok, bool* is_exit)
{
    //第六步内容
    //...
    
    //第七步新增内容
    //...
    
    //第八步新增内容
    std::cout << "[before free 1024*127 byte in " << name << "]" << std::endl;
    free(addr1);
    std::cout << "[after free 1024*127 byte in " << name << "]" << std::endl;
    getchar();
    std::cout << "[before free 1024*256 byte in " << name << "]" << std::endl;
    free(addr2);
    std::cout << "[after free 1024*256 byte in " << name << "]" << std::endl;
    getchar();   
    *is_ok = true;
    while (!*is_exit)
        std::this_thread::sleep_for(std::chrono::milliseconds(100));  
}

int main()
{
  //第一步内容
  //...
  
  //第二步内容
  //...
  
  //第三步内容
  //...
  
  //第四步内容
  //...
  
  //第五步内容
  //...
  
  //第六步内容:启动子线程执行以上5步
  //...
  return 0;
}

执行以上程序:strace -f -t -TT -e trace=brk,mmap,munmap ./a.out

新增内容
[before free 1024127 byte in thread0]
[after free 1024
127 byte in thread0]

[before free 1024256 byte in thread0]
[after free 1024
256 byte in thread0]

  • 子线程调用free并没有真正调用系统调用munmap将虚拟地址空间回收。这与主线程的free操作很不一样。

第九步,退出所有线程

void ThreadFun(std::string name, bool* is_ok, bool* is_exit)
{
    //第六步内容
    //...
    
    //第七步内容
    //...
    
    //第八步内容
    // ...
    
    //第九步内容
    *is_ok = true;
    while (!*is_exit)
        std::this_thread::sleep_for(std::chrono::milliseconds(100));   
}

int main()
{
  //第一步内容
  //...
  
  //第二步内容
  //...
  
  //第三步内容
  //...
  
  //第四步内容
  //...
  
  //第五步内容
  //...
  
  //第六步内容:启动子线程执行以上5步
  //...
  return 0;
}

执行以上程序:strace -f -t -TT -e trace=brk,mmap,munmap ./a.out

[pid 11765] 15:21:11 +++ exited with 0 +++
[pid 11517] 15:21:11 +++ exited with 0 +++
[pid 11676] 15:21:11 +++ exited with 0 +++
[pid 11647] 15:21:11 +++ exited with 0 +++
[pid 11725] 15:21:11 +++ exited with 0 +++
[pid 11434] 15:21:11 +++ exited with 0 +++
[pid 11717] 15:21:11 +++ exited with 0 +++
[pid 11766] 15:21:11 +++ exited with 0 +++
[pid 12010] 15:21:11 +++ exited with 0 +++
[pid 11804] 15:21:11 +++ exited with 0 +++
[pid 11767] 15:21:11 +++ exited with 0 +++
[pid 11686] 15:21:11 +++ exited with 0 +++
[pid 11890] 15:21:11 +++ exited with 0 +++
[pid 11929] 15:21:11 +++ exited with 0 +++
[pid 11736] 15:21:11 +++ exited with 0 +++
[pid 11919] 15:21:11 +++ exited with 0 +++
[pid 11718] 15:21:11 +++ exited with 0 +++
[pid 11714] 15:21:11 +++ exited with 0 +++
[pid 11554] 15:21:11 +++ exited with 0 +++
[pid 24826] 15:21:11 +++ exited with 0 +++
[pid 11763] 15:21:11 +++ exited with 0 +++
[pid 11809] 15:21:11 +++ exited with 0 +++
[pid 11723] 15:21:11 +++ exited with 0 +++
[pid 11632] 15:21:11 +++ exited with 0 +++
[pid 10621] 15:21:11 munmap(0x7fb884d4c000, 8392704) = 0 <0.000804>
[pid 11715] 15:21:11 +++ exited with 0 +++
[pid 11764] 15:21:11 +++ exited with 0 +++
[pid 11675] 15:21:11 +++ exited with 0 +++
[pid 11724] 15:21:11 +++ exited with 0 +++
[pid 11637] 15:21:11 +++ exited with 0 +++
[pid 10621] 15:21:11 munmap(0x7fb88454b000, 8392704) = 0 <0.000069>
[pid 10621] 15:21:11 munmap(0x7fb87f7ff000, 8392704) = 0 <0.000049>
[pid 10621] 15:21:11 munmap(0x7fb87effe000, 8392704) = 0 <0.000134>
[pid 10621] 15:21:11 munmap(0x7fb87e7fd000, 8392704) = 0 <0.000050>
[pid 10621] 15:21:11 munmap(0x7fb87dffc000, 8392704) = 0 <0.000042>
[pid 10621] 15:21:11 munmap(0x7fb87d7fb000, 8392704) = 0 <0.000059>
[pid 10621] 15:21:11 munmap(0x7fb87cffa000, 8392704) = 0 <0.000069>
[pid 11849] 15:21:11 +++ exited with 0 +++
[pid 11848] 15:21:11 +++ exited with 0 +++
[pid 11716] 15:21:11 +++ exited with 0 +++
15:21:11 munmap(0x7fb87c7f9000, 8392704) = 0 <0.000055>
15:21:11 munmap(0x7fb85b7ff000, 8392704) = 0 <0.000074>
15:21:11 munmap(0x7fb85affe000, 8392704) = 0 <0.000072>
15:21:11 munmap(0x7fb85a7fd000, 8392704) = 0 <0.000086>
15:21:11 munmap(0x7fb859ffc000, 8392704) = 0 <0.000056>
15:21:11 munmap(0x7fb8597fb000, 8392704) = 0 <0.000052>
15:21:11 munmap(0x7fb858ffa000, 8392704) = 0 <0.000058>
15:21:11 munmap(0x7fb8587f9000, 8392704) = 0 <0.000051>
15:21:11 munmap(0x7fb83b7ff000, 8392704) = 0 <0.000050>
15:21:11 munmap(0x7fb83affe000, 8392704) = 0 <0.000048>
15:21:11 munmap(0x7fb83a7fd000, 8392704) = 0 <0.000047>
15:21:11 munmap(0x7fb839ffc000, 8392704) = 0 <0.000051>
15:21:11 munmap(0x7fb8397fb000, 8392704) = 0 <0.000050>
15:21:11 munmap(0x7fb838ffa000, 8392704) = 0 <0.000050>
15:21:11 munmap(0x7fb8387f9000, 8392704) = 0 <0.000058>
15:21:11 munmap(0x7fb81b7ff000, 8392704) = 0 <0.000052>
15:21:11 munmap(0x7fb81affe000, 8392704) = 0 <0.000048>
15:21:11 munmap(0x7fb81a7fd000, 8392704) = 0 <0.000052>
15:21:11 munmap(0x7fb819ffc000, 8392704) = 0 <0.000048>
15:21:11 munmap(0x7fb8197fb000, 8392704) = 0 <0.000051>
15:21:11 +++ exited with 0 +++

  • 线程退出时,会调用munmap释放申请的栈空间,但并没有释放堆申请的虚拟地址空间

总结

1、main所在线程和子线程堆上申请内存的方式不太一样。子线程堆上申请内存时,会调用mmap映射一片虚拟地址空间,在此空间上由glibc的分配策略管理返回内存,并且即使子线程退出后也不一定会调用munmap释放虚拟地址空间。然而,主线程上释放空间,会立马调用munmap回收虚拟地址空间。

2、查资料发现,glibc为了处理复杂的多线程内存分配问题,线程和线程之间不一定会共用同一个堆,可能是为了处理申请内存时的竞争问题。但同时引入了新的问题,子线程堆上申请的空间,释放后虚拟地址空间不会回收,导致pss长时间不下降。

3、查资料发现,可以调用export MALLOC_ARENA_MAX=1,设置该环境变量,多线程之间就可以复用一个堆了,但会引入申请堆内存时,多线程之间的竞争问题。有研究发现,当虚拟地址真的不足时,malloc还是会调用mmap重新向系统要一个堆,在该堆分配和缓存虚拟地址空间。

参考资料

https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/comment-page-1/

https://www.easyice.cn/archives/341

你可能感兴趣的:(理解glibc中关于malloc的实现策略)