内存碎片研究

本文最后更新于:2025年11月19日 下午

前言

最近在一个项目上,遇到进程随运行时间内存一直增长,增长有10M之多。而且不下降。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
VmPeak:   203528 kB
VmSize: 132824 kB
VmLck: 0 kB
VmPin: 0 kB
VmHWM: 16104 kB
VmRSS: 13536 kB
RssAnon: 8628 kB
RssFile: 4908 kB
RssShmem: 0 kB
VmData: 123700 kB
VmStk: 132 kB
VmExe: 52 kB
VmLib: 6148 kB
VmPTE: 60 kB
VmSwap: 0 kB

最开始以为有内存泄露,使用valgrind检查了下,并没有发现泄露。而且随着测试时间增长,内存增长会变慢,甚至不增长。
于是怀疑是内存碎片。

内存碎片

以前也听说过内存碎片,但碎片占这么大内存空间(估摸至少浪费7,8M)还没遇到过。遂去网上查了一下资料。发现网上居然还有碎片1G以上的。

内存碎片离不开内存分配原理,这点比较复杂,简要总结下:
(1)glibc使用的内存分配器名字为ptmalloc

(2)分配内存有两个系统调用brk和mmap,小内存(默认低于128KB)使用brk,大内存使用mmap。

(3)brk是修改堆顶指针,所以只有当堆顶的内存释放后,堆占用大小才有可能收缩。
可通过cat /proc/pid/smaps查看到堆占用大小,可知当前堆大小为992KB

1
2
3
4
5
6
0002e000-00154000 rw-p 00000000 00:00 0          [heap]
Size: 1176 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Rss: 992 kB
Pss: 992 kB

(4)ptmalloc分配向系统申请的内存,程序free后不会立即释放,而是自己先管着,伺机还给系统。

(5)ptmalloc有一个主分配区,多个副分配区。多线程时,如果当前线程无法立即在主分配区持有锁,那么会找副分配区,如果也没有,那么会建立一个新的副分配区。
32位系统分配区个数限制为2 * CPU核数个,64位系统限制为 8 * CPU核数个。
副分配区通过mmap一次向os申请64MB(?)内存,一旦申请了,该分配区就不会释放。

检查了下代码(glibc-2.30),开辟分配区时,64位系统才一次申请64MB。32位一次分配2M(函数new_heap)。但这个仍然和malloc_stats打印的对不上。

查看malloc碎片情况

(1)使用malloc_stats函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Arena 0:
system bytes = 2625536
in use bytes = 1063008
Arena 1:
system bytes = 2011136
in use bytes = 314864
Arena 2:
system bytes = 2318336
in use bytes = 879032
Arena 3:
system bytes = 2527232
in use bytes = 475776
Total (incl. mmap):
system bytes = 9482240
in use bytes = 2732680
max mmap regions = 13
max mmap bytes = 5701632

system bytes表明向系统申请的,in use bytes表明使用中的。
从Total来看,还有6.7MB内存空闲着。

(2)使用mallinfo函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void display_mallinfo(void)
{
struct mallinfo mi;
mi = mallinfo();
printf("Total non-mmapped bytes (arena): %d\n", mi.arena);
printf("# of free chunks (ordblks): %d\n", mi.ordblks);
printf("# of free fastbin blocks (smblks): %d\n", mi.smblks);
printf("# of mapped regions (hblks): %d\n", mi.hblks);
printf("Bytes in mapped regions (hblkhd): %d\n", mi.hblkhd);
printf("Max. total allocated space (usmblks): %d\n", mi.usmblks);
printf("Free bytes held in fastbins (fsmblks): %d\n", mi.fsmblks);
printf("Total allocated space (uordblks): %d\n", mi.uordblks);
printf("Total free space (fordblks): %d\n", mi.fordblks);
printf("Topmost releasable block (keepcost): %d\n", mi.keepcost);
}

打印如下:

1
2
3
4
5
6
7
8
9
10
Total non-mmapped bytes (arena):       9482240
# of free chunks (ordblks): 1072
# of free fastbin blocks (smblks): 37
# of mapped regions (hblks): 0
Bytes in mapped regions (hblkhd): 0
Max. total allocated space (usmblks): 0
Free bytes held in fastbins (fsmblks): 1504
Total allocated space (uordblks): 2732680
Total free space (fordblks): 6749560
Topmost releasable block (keepcost): 96936

这个没有分区域,arena表明总消耗,uordblks表明总的正在使用,fordblks空闲的。

可调参数

(1)使用mallopt函数调整
调整方法如下:

1
2
3
mallopt(M_ARENA_MAX, 1);
mallopt(M_MMAP_THRESHOLD, 4*1024);
mallopt(M_TRIM_THRESHOLD, 4*1024);

所有参数见:mallopt(3) — Linux manual page

(2)使用glibc环境变量调整
调整方法如下:

1
2
GLIBC_TUNABLES=glibc.malloc.trim_threshold=128:glibc.malloc.check=3
export GLIBC_TUNABLES

所有参数见:38.2 Memory Allocation Tunables

解决办法?

替换glibc的malloc

使用tcmalloc, jemalloc这些
试过jemalloc,这个貌似不太适合嵌入式。初始内存占用更多了,可能需要调些参数。
tcmalloc没试,c++的代码,这个也是新的编译框架。

调整malloc参数

(1)调整M_MMAP_THRESHOLD,让更多的分配走mmap,这不会产生碎片。但是性能可能降低。
(2)调整M_ARENA_MAX,减少分配区个数,减少碎片。但是性能可能会降低。
分配区个数减少为1后,性能几乎没变。检查内存占用,发现内存主要都在堆上了。以前堆上内存只有1M的样子。

1
2
3
4
5
6
010da000-01716000 rw-p 00000000 00:00 0          [heap]
Size: 6384 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Rss: 6124 kB
Pss: 6124 kB

经测试,问题进程将内存分配区域设置为1,MMAP阈值和修剪阈值设置为64KB。碎片会减少很多。

1
2
3
mallopt(M_ARENA_MAX, 1);
mallopt(M_MMAP_THRESHOLD, 64*1024);
mallopt(M_TRIM_THRESHOLD, 64*1024);

调整前后对比:
测试方法:进程起来后,下载110MB的文件20次。查看进程的RSS内存。

对比项 RSS system bytes in use bytes 碎片占用
修改前 11704 KB 7925760 2487016 5483 KB
修改后 7068KB 1925120 1079808 845 KB

参考

linux环境内存分配原理–虚拟内存 mallocinfo
# mallinfo(3) — Linux manual page
glibc内存管理——Linux内存管理小结二
2万字|带你领略glibc内存管理精髓


内存碎片研究
https://leon0625.github.io/2024/01/05/c8daabb7fcee/
作者
leon.liu
发布于
2024年1月5日
许可协议