Linux虚拟内存管理

时间:2022-12-26 22:29:40

Linux的虚拟内存管理有几个关键概念:

  1. 每个进程有独立的虚拟地址空间,进程访问的虚拟地址空间并不是真正的物理地址
  2. 虚拟地址可通过每个进程上页表与物理地址进行映射,获得真正的物理地址
  3. 如果虚拟地址所对应的物理地址不在物理内存中,则产生缺页中断,真正分配物理地址,同时更新进程的页表;如果此时物理内存已经耗尽,则根据内存替换算法淘汰部分页面至物理磁盘中。

一. Linux虚拟地址空间如何分布?32位和64位有何不同?

Linux使用虚拟地址空间,大大增加了进程的寻址空间,由低地址到高地址分别是:

  1. 只读段:该部分空间只能读,不能写,包括代码段,rodata段(C常量字符和#define定义的常量)
  2. 数据段:保存全局变量、静态空间变量
  3. 堆:就是平时所说的动态内存,malloc/new大部分都源于此。其中堆顶的位置可以通过brk和sbrk进行动态调整
  4. 文件映射区域:如动态库,共享内存等映射物理空间的内存,一般是mmap函数所分配的虚拟地址空间
  5. 栈:用于维护函数调用的上下文空间,一般为8M,可以通过ulimit -s查看
  6. 内核虚拟空间:用户代码不可见的区域,由内核管理

下图是32位OS典型的虚拟地址空间分布:
Linux虚拟内存管理

32位系统有4G的地址空间,其中0X08048000~0Xbfffffff是用户空间,0Xc0000000~0Xffffffff是内核空间,包含内核代码和数据、与进程相关的数据结构(如页表,内核栈)等。另外%esp执行栈顶,往低地址方向变化;brk/sbrk函数控制堆顶往高地址方向变化。

可以通过以下代码验证进程的地址空间分布,其中sbrk(0)函数用于返回栈顶指针。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int global_num = 0;
char global_str_arr[65536] = { 'a' };
int main(int argc, char** argv)
{
char* heap_var = NULL;
int local_var = 0;
printf("Address of function main 0x%lx\n", main);
printf("Address of global_num 0x%lx\n", &global_num);
printf("Address of global_str_arr 0x%lx ~ 0x%lx\n", &global_str_arr[0], &global_str_arr[65535]);
printf("Top of stack is 0x%lx\n", &local_var);
printf("Top of heap is 0x%lx\n", sbrk(0));
heap_var = malloc(sizeof(char)* 127 * 1024);
printf("Address of heap_var is 0x%lx\n", heap_var);
printf("Top of heap after malloc is 0x%lx\n", sbrk(0));
free(heap_var);
heap_var = NULL;
printf("Top of heap after free is 0x%lx\n", sbrk(0));
return 1;
}

32位系统的结果如下,与上图的划分保持一致,并且栈顶指针在malloc和free一个127K的存储空间时都发生了变化(增大和缩小)

Address of function main 0x8048474
Address of global_num 0x8059904
Address of global_str_arr 0x8049900 ~ 0x80598ff
Top of stack is 0xbfd0886c
Top of heap is 0x805a000
Address of heap_var is 0x805a008
Top of heap after malloc is 0x809a000
Top of heap after free is 0x807b000

但是64位系统的结果怎样呢?64位系统是否拥有2^64的地址空间呢?
64位的结果如下:

Address of function main 0x400594
Address of global_num 0x610b90
Address of global_str_arr 0x600b80 ~ 0x610b7f
Top of stack is 0x7fff2e9e4994
Top of heap is 0x8f5000
Address of heap_var is 0x8f5010
Top of heap after malloc is 0x935000
Top of heap after free is 0x916000

从结果可知,与上图的分布并不一致。而事实上,64位系统的虚拟地址空间划分发生了变化:

  1. 地址空间大小不是2^32,也不是2^64,而一般是2^48。因为并不需要2^64这么大的寻址空间,过大空间只会导致资源的浪费。64位Linux一般使用48位来表示虚拟地址空间,40位表示物理地址,可通过/proc/cpuinfo来查看:
    address sizes : 40 bits physical, 48 bits virtual
  2. 其中,0x0000000000000000~0x00007fffffffffff表示用户空间,0xFFFF800000000000~ 0xFFFFFFFFFFFFFFFF表示内核空间,共提供 256TB(2^48) 的寻址空间。这两个区间的特点是,第47位与48~63位相同,若这些位为0表示用户空间,否则表示内核空间
  3. 用户空间由低地址到高地址仍然是只读段,数据段,堆,文件映射区域和栈

二.malloc是如何分配内存的?

malloc是glibc中的内存分配函数,也是最常用的动态内存分配函数,其内存必须通过free进行释放,否则导致内存泄漏。
关于malloc获得虚拟空间的表现,与glibc的版本有关,但大体逻辑上:

1.若分配内存小于128K,调用sbrk(),将堆顶指针向高地址移动,获得新的虚拟空间;
2.若分配内存大于128K,调用mmap(),在文件映射区域中分配匿名虚拟空间;
3.这里讨论的是简单情况,如果涉及并发则可能会复杂一些,不过先不讨论。
其中sbrk()就是修改栈顶指针位置,而mmap可用于生成文件的映射以及修改匿名页面的内存,这里指的是匿名页面。

而这个128K,是glibc的默认配置,可通过函数mallopt来设置,可通过以下例子来说明:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <malloc.h>
void print_info(
char* var_name,
char* var_ptr,
size_t size_in_kb
)

{
printf("Address of %s(%luk) 0x%lx, now heap top is 0x%lx\n",
var_name, size_in_kb, var_ptr, sbrk(0));
}
int main(int argc, char** argv)
{
char *heap_var1, *heap_var2, *heap_var3 ;
char *mmap_var1, *mmap_var2, *mmap_var3 ;
char *maybe_mmap_var;
printf("Orginal heap top is 0x%lx\n", sbrk(0));
heap_var1 = malloc(32*1024);
print_info("heap_var1", heap_var1, 32);
heap_var2 = malloc(64*1024);
print_info("heap_var2", heap_var2, 64);
heap_var3 = malloc(127*1024);
print_info("heap_var3", heap_var3, 127);
printf("\n");
maybe_mmap_var = malloc(128*1024);
print_info("maybe_mmap_var", maybe_mmap_var, 128);
//mmap
mmap_var1 = malloc(128*1024);
print_info("mmap_var1", mmap_var1, 128);
// set M_MMAP_THRESHOLD to 64k
mallopt(M_MMAP_THRESHOLD, 64*1024);
printf("set M_MMAP_THRESHOLD to 64k\n");
mmap_var2 = malloc(64*1024);
print_info("mmap_var2", mmap_var2, 64);
mmap_var3 = malloc(127*1024);
print_info("mmap_var3", mmap_var3, 127);
return 1;
}

这个例子很简单,通过 malloc 申请多个不同大小的动态内存,同时通过接口 print_info 打印变量大小和地址等相关信息,其中 sbrk(0) 可返回堆顶指针位置。另外,粗体部分是将 MMAP 分配的临界点由 128k 转为 64k ,再打印变量地址的不同。

下面是 Linux 64 位机器的执行结果(后文所有例子都是通过 64 位机器上的测试结果):

Orginal heap top is 0x17da000
Address of heap_var1(32k) 0x17da010, now heap top is 0x1803000
Address of heap_var2(64k) 0x17e2020, now heap top is 0x1803000
Address of heap_var3(127k) 0x17f2030, now heap top is 0x1832000
Address of maybe_mmap_var(128k) 0x1811c40, now heap top is 0x1832000
Address of mmap_var1(128k) 0x7f4a0b1f2010, now heap top is 0x1832000
set M_MMAP_THRESHOLD to 64k
Address of mmap_var2(64k) 0x7f4a0b1e1010, now heap top is 0x1832000
Address of mmap_var3(127k) 0x7f4a0b1c1010, now heap top is 0x1832000

三.malloc分配多大的内存,就占用多大的物理内存空间吗?

malloc分配的内存是虚拟地址空间,而虚拟地址空间和物理地址空间使用进程页表进行映射,那么分配了空间就是占用物理内存空间了吗?

首先,进程使用了多少内存可通过ps -aux命令查看,其中关键的两个信息(第五,六列)为:
1. VSZ,virtual memory size,表示进程总共使用的虚拟地址空间大小,包括进程地址空间的代码段,数据段,堆,文件映射区域,栈,内核空间等所有虚拟地址使用的总和,单位为K。
2. RSS,resident set size,表示进程实际使用的物理空间大小,RSS总小于VSZ

可通过一个例子说明这个问题 :

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <malloc.h>
char ps_cmd[1024];
void print_info(
char* var_name,
char* var_ptr,
size_t size_in_kb
)

{
printf("Address of %s(%luk) 0x%lx, now heap top is 0x%lx\n",
var_name, size_in_kb, var_ptr, sbrk(0));
system(ps_cmd);
}

int main(int argc, char** argv)
{
char *non_set_var, *set_1k_var, *set_5k_var, *set_7k_var;
pid_t pid;
pid = getpid();
sprintf(ps_cmd, "ps aux | grep %lu | grep -v grep", pid);
non_set_var = malloc(32*1024);
print_info("non_set_var", non_set_var, 32);
set_1k_var = malloc(64*1024);
memset(set_1k_var, 0, 1024);
print_info("set_1k_var", set_1k_var, 64);
set_5k_var = malloc(127*1024);
memset(set_5k_var, 0, 5*1024);
print_info("set_5k_var", set_5k_var, 127);
set_7k_var = malloc(64*1024);
memset(set_1k_var, 0, 7*1024);
print_info("set_7k_var", set_7k_var, 64);
return 1;
}

该代码扩展了上一个例子print_info能力,处理打印变量信息,同时通过 ps aux 命令获得当前进程的 VSZ 和 RSS 值。并且程序 malloc 一块内存后,会 memset 内存的若干 k 内容。

执行结果为:

Address of non_set_var(32k) 0x502010,  now heap top is 0x52b000

mysql 12183 0.0 0.0 2692 452 pts/3 S+ 20:29 0:00 ./test_vsz

Address of set_1k_var(64k) 0x50a020, now heap top is 0x52b000

mysql 12183 0.0 0.0 2692 456 pts/3 S+ 20:29 0:00 ./test_vsz

Address of set_5k_var(127k) 0x51a030, now heap top is 0x55a000

mysql 12183 0.0 0.0 2880 464 pts/3 S+ 20:29 0:00 ./test_vsz

Address of set_7k_var(64k) 0x539c40, now heap top is 0x55a000

mysql 12183 0.0 0.0 2880 472 pts/3 S+ 20:29 0:00 ./test_vsz

由以上结果可知:
1. VSZ并不是每次malloc后都增长,是与上一节说的堆顶没发生变化有关,因为可重用堆顶内剩余的空间,这样malloc是很轻量和快速的;
2. 如果VSZ发生变化,基本与分配内存量相当,因为VSZ是计算虚拟地址空间总大小;
3. RSS的增量很少,是因为malloc分配的内存并不就马上分配实际的存储空间,只有第一次使用,如第一个memset后才分配;
4. 由于每个物理页面大小是4K,不管memset其中的1K,还是5K,7K,实际占用物理内存总是4K的倍数。所以RSS的增量总是4K的倍数;
5. 因此,不是malloc后马上占用实际内存,而是第一次使用时发现虚存对应的物理页面未分配,产生缺页中断,才真正分配物理页面,同时更新进程页表的映射关系。这也是Linux虚拟内存管理的核心概念之一。

四.如何查看进程虚拟地址空间的使用情况

进程地址空间被分成了代码段,数据段,堆,文件映射区域,栈等区域,怎么查询这些虚拟地址空间的使用情况呢?
Linux提供了pmap命令来查看这些信息,通常使用pmap -d pid(高版本可提供pmap -x pid)查询,如下所示:

mysql@ TLOG_590_591:~/vin/test_memory> pmap -d 17867

17867: test_mmap

START SIZE RSS DIRTY PERM OFFSET DEVICE MAPPING

00400000 8K 4K 0K r-xp 00000000 08:01 /home/mysql/vin/test_memory/test_mmap

00501000 68K 8K 8K rw-p 00001000 08:01 /home/mysql/vin/test_memory/test_mmap

00512000 76K 0K 0K rw-p 00512000 00:00 [heap]

0053e000 256K 0K 0K rw-p 0053e000 00:00 [anon]

2b3428f97000 108K 92K 0K r-xp 00000000 08:01 /lib64/ld-2.4.so

2b3428fb2000 8K 8K 8K rw-p 2b3428fb2000 00:00 [anon]

2b3428fc1000 4K 4K 4K rw-p 2b3428fc1000 00:00 [anon]

2b34290b1000 8K 8K 8K rw-p 0001a000 08:01 /lib64/ld-2.4.so

2b34290b3000 1240K 248K 0K r-xp 00000000 08:01 /lib64/libc-2.4.so

2b34291e9000 1024K 0K 0K ---p 00136000 08:01 /lib64/libc-2.4.so

2b34292e9000 12K 12K 12K r--p 00136000 08:01 /lib64/libc-2.4.so

2b34292ec000 8K 8K 8K rw-p 00139000 08:01 /lib64/libc-2.4.so

2b34292ee000 1048K 36K 36K rw-p 2b34292ee000 00:00 [anon]

7fff81afe000 84K 12K 12K rw-p 7fff81afe000 00:00 [stack]

ffffffffff600000 8192K 0K 0K ---p 00000000 00:00 [vdso]

Total: 12144K 440K 96K

从这个结果可以看到进程虚拟地址空间的使用情况,包括起始地址、大小、实际使用内存、脏页大小、权限、偏移、设备和映射文件等,pmap命令就是基于下面两文件进行解析的:

/proc/pid/maps
/proc/pid/smaps

并且对于上述每个内存块区间,内核会使用一个vm_area_struct结构来维护,同时提供页面建立与物理内存的映射关系,如下图所示:
Linux虚拟内存管理

五.free的内存真的释放了吗(还给OS)?

前面的示例都有一个严重的问题,就是分配的内存都没有释放,即导致内存泄漏,原则上所有malloc/new分配的内存,都需要free/delete来释放,但是free了的内存真的释放了吗?

要说清楚这个问题,可通过下面例子来说明:

1.初始状态:如图 (1) 所示,系统已分配 ABCD 四块内存,其中 ABD 在堆内分配, C 使用 mmap 分配。为简单起见,图中忽略了如共享库等文件映射区域的地址空间。

2.E=malloc(100K):分配 100k 内存,小于 128k ,从堆内分配,堆内剩余空间不足,扩展堆顶 (brk) 指针。

3.free(A):释放 A 的内存,在 glibc 中,仅仅是标记为可用,形成一个内存空洞 ( 碎片 ) ,并没有真正释放。如果此时需要分配 40k 以内的空间,可重用此空间,剩余空间形成新的小碎片。

Linux虚拟内存管理

4.free(C):C 空间大于 128K ,使用 mmap 分配,如果释放 C ,会调用 munmap 系统调用来释放,并会真正释放该空间,还给 OS ,如图 (4) 所示。

5.free(D):与释放 A 类似,释放 D 同样会导致一个空洞,获得空闲空间,但并不会还给 OS 。此时,空闲总空间为 100K ,但由于虚拟地址不连续,无法合并,空闲空间无法满足大于 60k 的分配请求。

6.free(E):释放 E ,由于与 D 连续,两者将进行合并,得到 160k 连续空闲空间。同时 E 是最靠近堆顶的空间,glibc 的 free 实现中,只要堆顶附近释放总空间(包括合并的空间)超过 128k ,即会调用 sbrk(-SIZE) 来回溯堆顶指针,将原堆顶空间还给 OS ,如图 (6) 所示。而堆内的空闲空间还是不会归还 OS 的。

Linux虚拟内存管理

由此可见:
1. malloc使用mmap分配的内存(大于128K),free会调用unmap系统调用马上还给OS,实现真正释放。
2. 堆内的内存,只有释放堆顶的空间,同时堆顶总连续空间大于128K才使用sbrk(-SIZE)回收内存,真正归还OS。
3. 堆内的空闲空间,是不会归还给OS的。

六.程序代码中malloc的内存都有相应的free,就不会出现内存泄漏了吗?

狭义上的内存泄漏是指malloc的内存,没有free,导致内存泄漏,直到程序结束。而广义上的内存泄漏是进程使用内存量不断增加,或大大超出了系统原设计的上限。

上一节说到,free 了的内存并不会马上归还 OS ,并且堆内的空洞(碎片)更是很难真正释放,除非空洞成为了新的堆顶 。所以,如上一例子情况 (5) ,释放了 40k 和 60k 两片内存,但如果此时需要申请大于 60k (如 70k ),没有可用碎片,必须向 OS 申请,实际使用内存仍然增大。

因此,随着系统频繁的malloc和free,尤其是对于小块内存,堆内将产生越来越多不可用的碎片,导致“内存泄漏”。而这种“泄露”现象使用 valgrind 是无法检测出来的。

下图是 MySQL 存在大量分区表时的内存使用情况 (RSS 和 VSZ) ,疑似“内存泄露”。
Linux虚拟内存管理

因此,当我们写程序时,不能完全依赖于glibc的malloc和free的实现。更好方式是建立进程的内存池,即一次分配(malloc)大块内存,小内存从内存池中获得,当进程结束或该块内存不可用时,一次释放(free),可大大减少碎片的产生。

七.既然堆内内存不能直接释放,为什么不全部使用mmap来分配

由于堆内碎片不能直接释放,而问题5中说到的mmap分配的内存可以通过unmap进行free,实现真正释放。既然堆内碎片不能直接释放,导致疑似“内存泄漏”问题,为什么malloc不全部使用mmap来实现呢?而仅仅对于大于128K的大块内存才使用mmap?

其实,进程向OS申请和释放紧致空间的接口 sbrk/mmap/unmap都是系统调用,频繁的系统调用都比较消耗系统资源。并且,mmap申请的内存被unmap后,重新申请会产生更多的缺页中断。例如mmap分配 1M 空间,第一次调用产生了大量缺页中断(1M/4K次),当unmap后再次分配 1M 空间,会再次产生大量缺页中断。缺页中断属于内核行为,会导致内核态 CPU 消耗较大。另外,使用 mmap 分配小内存,会导致地址空间的分片更多,内核的管理负担更大。

而堆是一个连续空间,并且堆内碎片由于没有归还给OS,如果可重用碎片,在此访问该内存很可能不需要任何系统调用和缺页中断,这将大大降低CPU的消耗。

因此,glibc的malloc实现中,充分考虑了sbrk和mmap行为上的差异和优缺点,默认分配大块内存(128K)才使用mmap获得地址空间,也可通过 mallopt(M_MMAP_THRESHOLD,SIZE)来修改这个临界值。

八.如何查看进程的缺页中断信息?

可通过以下命令查看缺页中断信息:

ps -o majflt,minflt -C <program_name>
ps -o majflt,minflt -p <pid>

其中, majflt 代表 major fault ,指大错误, minflt 代表 minor fault ,指小错误。这两个数值表示一个进程自启动以来所发生的缺页中断的次数。其中 majflt 与 minflt 的不同是, majflt 表示需要读写磁盘,可能是内存对应页面在磁盘中需要 load 到物理内存中,也可能是此时物理内存不足,需要淘汰部分物理页面至磁盘中。

例如,下面是 mysqld 的一个例子。

mysql@ TLOG_590_591:~> ps -o majflt,minflt -C mysqld
MAJFLT MINFLT
144856 15296294

如果进程的内核态 CPU 使用过多,其中一个原因就可能是单位时间的缺页中断次数多个,可通过以上命令来查看。

如果 MAJFLT 过大,很可能是内存不足。
如果 MINFLT 过大,很可能是频繁分配 / 释放大块内存 (128k) , malloc 使用 mmap 来分配。对于这种情况,可通过 mallopt(M_MMAP_THRESHOLD, SIZE)增大临界值,或程序实现内存池。

九.如何查看堆内内存的碎片情况?

glibc 提供了以下结构和接口来查看堆内内存和 mmap 的使用情况:

struct mallinfo {
int arena; /* non-mmapped space allocated from system */
int ordblks; /* number of free chunks */
int smblks; /* number of fastbin blocks */
int hblks; /* number of mmapped regions */
int hblkhd; /* space in mmapped regions */
int usmblks; /* maximum total allocated space */
int fsmblks; /* space available in freed fastbin blocks */
int uordblks; /* total allocated space */
int fordblks; /* total free space */
int keepcost; /* top-most, releasable (via malloc_trim) space */
};


/* 返回 heap(main_arena) 的内存使用情况,以 mallinfo 结构返回 */
struct mallinfo mallinfo();
/* 将 heap 和 mmap 的使用情况输出到 stderr */
void malloc_stats();

可通过以下例子来验证 mallinfo 和 malloc_stats 输出结果:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <malloc.h>
size_t heap_malloc_total, heap_free_total,
mmap_total, mmap_count;
void print_info()
{
struct mallinfo mi = mallinfo();
printf("count by itself:\n");
printf("\theap_malloc_total=%lu heap_free_total=%lu heap_in_use=%lu\n\
\tmmap_total=%lu mmap_count=%lu\n"
,
heap_malloc_total*1024, heap_free_total*1024, heap_malloc_total*1024 - heap_free_total*1024,
mmap_total*1024, mmap_count);
printf("count by mallinfo:\n");
printf("\theap_malloc_total=%lu heap_free_total=%lu heap_in_use=%lu\n\
\tmmap_total=%lu mmap_count=%lu\n"
,
mi.arena, mi.fordblks, mi.uordblks,
mi.hblkhd, mi.hblks);
printf("from malloc_stats:\n");
malloc_stats();
}

#define ARRAY_SIZE 200
int main(int argc, char** argv)
{
char** ptr_arr[ARRAY_SIZE];
int i;
for( i = 0; i < ARRAY_SIZE; i++) {
ptr_arr[i] = malloc(i * 1024);
if ( i < 128)
heap_malloc_total += i;
else {
mmap_total += i;
mmap_count++;
}

}
print_info();
for( i = 0; i < ARRAY_SIZE; i++) {
if ( i % 2 == 0)
continue;
free(ptr_arr[i]);
if ( i < 128)
heap_free_total += i;
else {
mmap_total -= i;
mmap_count--;
}
}
printf("\nafter free\n");
print_info();
return 1;
}

该例子第一个循环为指针数组每个成员分配索引位置 (KB) 大小的内存块,并通过 128 为分界分别对 heap 和 mmap 内存分配情况进行计数;第二个循环是 free 索引下标为奇数的项,同时更新计数情况。通过程序的计数与 mallinfo/malloc_stats 接口得到结果进行对比,并通过 print_info 打印到终端。

下面是一个执行结果:

count by itself:
heap_malloc_total=8323072 heap_free_total=0 heap_in_use=8323072
mmap_total=12054528 mmap_count=72
count by mallinfo:
heap_malloc_total=8327168 heap_free_total=2032 heap_in_use=8325136
mmap_total=12238848 mmap_count=72
from malloc_stats:
Arena 0:
system bytes = 8327168
in use bytes = 8325136
Total (incl. mmap):
system bytes = 20566016
in use bytes = 20563984
max mmap regions = 72
max mmap bytes = 12238848

after free

count by itself:
heap_malloc_total=8323072 heap_free_total=4194304 heap_in_use=4128768
mmap_total=6008832 mmap_count=36
count by mallinfo:
heap_malloc_total=8327168 heap_free_total=4197360 heap_in_use=4129808
mmap_total=6119424 mmap_count=36
from malloc_stats:
Arena 0:
system bytes = 8327168
in use bytes = 4129808
Total (incl. mmap):
system bytes = 14446592
in use bytes = 10249232
max mmap regions = 72
max mmap bytes = 12238848

由上可知,程序统计和 mallinfo 得到的信息基本吻合,其中 heap_free_total 表示堆内已释放的内存碎片总和。

如果想知道堆内片究竟有多碎 ,可通过 mallinfo 结构中的 fsmblks 、 smblks 、 ordblks 值得到,这些值表示不同大小区间的碎片总个数,这些区间分别是 0~80 字节, 80~512 字节, 512~128k 。如果 fsmblks 、 smblks 的值过大,那碎片问题可能比较严重了。

不过, mallinfo 结构有一个很致命的问题,就是其成员定义全部都是 int ,在 64 位环境中,其结构中的 uordblks/fordblks/arena/usmblks 很容易就会导致溢出,应该是历史遗留问题,使用时要注意!

十. 除了 glibc 的 malloc/free ,还有其他第三方实现吗?

其实,很多人开始诟病 glibc 内存管理的实现,就是在高并发性能低下和内存碎片化问题都比较严重,因此,陆续出现一些第三方工具来替换 glibc 的实现,最著名的当属 google 的 tcmalloc 和 facebook 的 jemalloc 。