最近在写一个程序,涉及到文件上传和下载,它大量用到动态内存分配,下载的时候会预缓存文件内容,因此下载的时候,内存占用可以达到20MB。但是空闲的时候,内存占用并没有降到和程序刚启动的时候一样低,而是有9M的样子。
我们知道,malloc在分配小块的时候,是使用的data segment区域的内存,它通过brk/sbrk来改变大小,这是一个连续的内存区域。这让我想到一个碎片化的问题:不断分配小块内存,直到100M,然后释放所有之前分配的,留最后一个块不释放,那么之前的也不能释放,因为释放需要改变数据段的大小,而低部被占用了,显然不能改变。那不就导致碎片化的问题了吗。
写一个程序测试:分配N个块,然后等待按键,然后释放前N-1个,并且调用malloc_trim()手动回收内存,然后暂停。等待按键和暂停是为了方便用meminfo工具读取内存占用情况。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <malloc.h> int main( int argc, char **argv) { int bs, N; int i; char **p; if (argc != 3) { fprintf (stderr, "invalid arguments\n" ); return 1; } bs = atoi (argv[1]); N = atoi (argv[2]); printf ( "malloc %d blocks, block size %d, pid %d\n" , N, bs, getpid()); p = malloc (N * sizeof ( void *)); if (!p) { fprintf (stderr, "malloc failed\n" ); return 1; } for (i = 0; i < N; i++) { p[i] = malloc (bs); if (!p[i]) { fprintf (stderr, "malloc failed\n" ); return 1; } memset (p[i], 0, bs); } printf ( "malloc done, press anykey to continue\n" ); getchar (); for (i = 0; i < N - 1; i++) free (p[i]); malloc_trim(0); printf ( "free done\n" ); pause(); return 1; } |
执行:
$ ./memfrag 100000 10
malloc 10 blocks, block size 100000, pid 1074320
malloc done, press anykey to continue
然后使用meminfo查看堆的内存情况:
$ sudo ./meminfo 1074320
free swapped exc shr ave
0KB 0KB 4KB 0KB 0KB 557ea77dc000-557ea77dd000 r--p 0 /work/test/memfrag
0KB 0KB 4KB 0KB 0KB 557ea77dd000-557ea77de000 r-xp 1000 /work/test/memfrag
0KB 0KB 4KB 0KB 0KB 557ea77de000-557ea77df000 r--p 2000 /work/test/memfrag
0KB 0KB 4KB 0KB 0KB 557ea77df000-557ea77e0000 r--p 2000 /work/test/memfrag
0KB 0KB 4KB 0KB 0KB 557ea77e0000-557ea77e1000 rw-p 3000 /work/test/memfrag
128KB 0KB 980KB 0KB 0KB 557ea7ff3000-557ea8108000 rw-p 0 [heap]
0KB 0KB 0KB 136KB 0KB 7f517e570000-7f517e592000 r--p 0 /usr/lib/x86_64-linux-gnu/libc-2.31.so
556KB 0KB 0KB 948KB 8KB 7f517e592000-7f517e70a000 r-xp 22000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
164KB 0KB 0KB 148KB 0KB 7f517e70a000-7f517e758000 r--p 19a000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
0KB 0KB 16KB 0KB 0KB 7f517e758000-7f517e75c000 r--p 1e7000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
0KB 0KB 8KB 0KB 0KB 7f517e75c000-7f517e75e000 rw-p 1eb000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
4KB 0KB 20KB 0KB 0KB 7f517e75e000-7f517e764000 rw-p 0
0KB 0KB 0KB 4KB 0KB 7f517e77a000-7f517e77b000 r--p 0 /usr/lib/x86_64-linux-gnu/ld-2.31.so
0KB 0KB 0KB 140KB 0KB 7f517e77b000-7f517e79e000 r-xp 1000 /usr/lib/x86_64-linux-gnu/ld-2.31.so
0KB 0KB 0KB 32KB 0KB 7f517e79e000-7f517e7a6000 r--p 24000 /usr/lib/x86_64-linux-gnu/ld-2.31.so
0KB 0KB 4KB 0KB 0KB 7f517e7a7000-7f517e7a8000 r--p 2c000 /usr/lib/x86_64-linux-gnu/ld-2.31.so
0KB 0KB 4KB 0KB 0KB 7f517e7a8000-7f517e7a9000 rw-p 2d000 /usr/lib/x86_64-linux-gnu/ld-2.31.so
0KB 0KB 4KB 0KB 0KB 7f517e7a9000-7f517e7aa000 rw-p 0
120KB 0KB 12KB 0KB 0KB 7fff7f887000-7fff7f8a8000 rw-p 0 [stack]
16KB 0KB 0KB 0KB 0KB 7fff7f8fd000-7fff7f901000 r--p 0 [vvar]
4KB 0KB 0KB 4KB 0KB 7fff7f901000-7fff7f903000 r-xp 0 [vdso]
0KB 0KB 0KB 0KB 0KB ffffffffff600000-ffffffffff601000 --xp 0 [vsyscall]
VSS 3472KB
RSS 2480KB
PSS 1080KB
USS 1068KB
可以看到有980KB的独占物理内存占用。按任意键释放内存,并调用malloc_trim()回收,然后再看内存占用:
$ sudo ./meminfo 1074320
free swapped exc shr ave
0KB 0KB 4KB 0KB 0KB 557ea77dc000-557ea77dd000 r--p 0 /work/test/memfrag
0KB 0KB 4KB 0KB 0KB 557ea77dd000-557ea77de000 r-xp 1000 /work/test/memfrag
0KB 0KB 4KB 0KB 0KB 557ea77de000-557ea77df000 r--p 2000 /work/test/memfrag
0KB 0KB 4KB 0KB 0KB 557ea77df000-557ea77e0000 r--p 2000 /work/test/memfrag
0KB 0KB 4KB 0KB 0KB 557ea77e0000-557ea77e1000 rw-p 3000 /work/test/memfrag
876KB 0KB 104KB 0KB 0KB 557ea7ff3000-557ea80e8000 rw-p 0 [heap]
0KB 0KB 0KB 136KB 0KB 7f517e570000-7f517e592000 r--p 0 /usr/lib/x86_64-linux-gnu/libc-2.31.so
556KB 0KB 0KB 948KB 8KB 7f517e592000-7f517e70a000 r-xp 22000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
164KB 0KB 0KB 148KB 0KB 7f517e70a000-7f517e758000 r--p 19a000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
0KB 0KB 16KB 0KB 0KB 7f517e758000-7f517e75c000 r--p 1e7000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
0KB 0KB 8KB 0KB 0KB 7f517e75c000-7f517e75e000 rw-p 1eb000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
4KB 0KB 20KB 0KB 0KB 7f517e75e000-7f517e764000 rw-p 0
0KB 0KB 0KB 4KB 0KB 7f517e77a000-7f517e77b000 r--p 0 /usr/lib/x86_64-linux-gnu/ld-2.31.so
0KB 0KB 0KB 140KB 0KB 7f517e77b000-7f517e79e000 r-xp 1000 /usr/lib/x86_64-linux-gnu/ld-2.31.so
0KB 0KB 0KB 32KB 0KB 7f517e79e000-7f517e7a6000 r--p 24000 /usr/lib/x86_64-linux-gnu/ld-2.31.so
0KB 0KB 4KB 0KB 0KB 7f517e7a7000-7f517e7a8000 r--p 2c000 /usr/lib/x86_64-linux-gnu/ld-2.31.so
0KB 0KB 4KB 0KB 0KB 7f517e7a8000-7f517e7a9000 rw-p 2d000 /usr/lib/x86_64-linux-gnu/ld-2.31.so
0KB 0KB 4KB 0KB 0KB 7f517e7a9000-7f517e7aa000 rw-p 0
120KB 0KB 12KB 0KB 0KB 7fff7f887000-7fff7f8a8000 rw-p 0 [stack]
16KB 0KB 0KB 0KB 0KB 7fff7f8fd000-7fff7f901000 r--p 0 [vvar]
4KB 0KB 0KB 4KB 0KB 7fff7f901000-7fff7f903000 r-xp 0 [vdso]
0KB 0KB 0KB 0KB 0KB ffffffffff600000-ffffffffff601000 --xp 0 [vsyscall]
VSS 3344KB
RSS 1604KB
PSS 204KB
USS 192KB
可以看到,独占的内存只有104KB了,大致只有一个块,但是虚拟地址空间范围没变。这是为啥呢?原来是madvise系统调用的功劳。
使用strace分析进程做了啥:
$ strace ./memfrag 100000 10
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x4), ...}) = 0
brk(NULL) = 0x557766f4a000
brk(0x557766f6b000) = 0x557766f6b000
write(1, "malloc 10 blocks, block size 100"..., 49malloc 10 blocks, block size 100000, pid 1074352
) = 49
brk(0x557766f9c000) = 0x557766f9c000
brk(0x557766fcd000) = 0x557766fcd000
brk(0x557766ffd000) = 0x557766ffd000
brk(0x55776702e000) = 0x55776702e000
brk(0x55776705f000) = 0x55776705f000
write(1, "malloc done, press anykey to con"..., 38malloc done, press anykey to continue
) = 38
fstat(0, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x4), ...}) = 0
read(0,
"\n", 1024) = 1
madvise(0x557766f4b000, 897024, MADV_DONTNEED) = 0
brk(0x55776703f000) = 0x55776703f000
write(1, "free done\n", 10free done
) = 10
madvise可以把data segment中间地址的物理内存释放。但是保留虚拟地址空间。
参考:
https://comp.unix.programmer.narkive.com/keelAZ81/memory-fragmentation-with-malloc
https://man7.org/linux/man-pages/man2/madvise.2.html