如何在tcmalloc中释放虚拟内存?Linux下有无可行方案?
问题描述
我在程序中使用tcmalloc进行内存分配与释放,调用MallocExtension::instance()->ReleaseFreeMemory()仅能释放物理内存,虚拟内存仍被持续占用。后续需要通过shmat将文件挂载到指定内存地址,若tcmalloc占用过多虚拟内存,会导致挂载地址无法使用。请问是否有办法在tcmalloc中释放虚拟内存,或是Linux下有可用的相关系统函数?
测试代码
int main() { int i = 0; while (i < 100) { char* a = (char*)malloc(1024 * 1024 * 2 * i); malloc_stats(); free(a); MallocExtension::instance()->ReleaseFreeMemory(); malloc_stats(); i++; usleep(1000 * 300); } // 最后一次循环中,实际内存占用小,但虚拟地址空间占用大... // 此时调用shmat挂载可能失败 return 0; }
内存统计信息
MALLOC: 207634928 ( 198.0 MiB) Bytes in use by application MALLOC: + 0 ( 0.0 MiB) Bytes in page heap freelist MALLOC: + 56408 ( 0.1 MiB) Bytes in central cache freelist MALLOC: + 0 ( 0.0 MiB) Bytes in transfer cache freelist MALLOC: + 440 ( 0.0 MiB) Bytes in thread cache freelists MALLOC: + 11251864 ( 10.7 MiB) Bytes in malloc metadata MALLOC: ------------ MALLOC: = 218943640 ( 208.8 MiB) Actual memory used (physical + swap) MALLOC: + 10130219008 ( 9660.9 MiB) Bytes released to OS (aka unmapped) MALLOC: ------------ MALLOC: = 10349162648 ( 9869.7 MiB) **Virtual address space used**
解决方案
1. 调整tcmalloc的配置与接口调用
提高内存释放速率:tcmalloc的
ReleaseFreeMemory()行为受release_rate参数控制,默认值为1.0,值越大越积极地将空闲内存归还给OS(包括unmap虚拟内存)。可以在代码中设置:MallocExtension::instance()->SetReleaseRate(10.0); // 提高释放速率 MallocExtension::instance()->ReleaseFreeMemory();也可以通过环境变量
TCMALLOC_RELEASE_RATE=10设置。清空线程缓存:线程缓存会持有部分空闲内存,导致虚拟地址无法被unmap。调用以下接口清空线程缓存后再释放内存:
MallocExtension::instance()->FlushThreadCache(); MallocExtension::instance()->ReleaseFreeMemory();限制线程缓存大小或禁用:通过环境变量
TCMALLOC_MAX_TOTAL_THREAD_CACHE_BYTES=0禁用线程缓存,或者设置一个较小的值,减少虚拟地址碎片的产生。限制arena数量:tcmalloc默认会为每个CPU核心创建arena,过多arena会导致虚拟地址空间碎片化。通过环境变量
MALLOC_ARENA_MAX=2(根据实际CPU核心数调整)限制arena数量,降低虚拟内存占用。
2. Linux系统层面的补充手段
使用
madvise标记内存可释放:对于tcmalloc管理的大内存块(通常是超过1MB的分配),可以在free前调用madvise标记,帮助内核和tcmalloc更高效地回收虚拟地址:char* a = (char*)malloc(1024 * 1024 * 2 * i); // 使用内存... madvise(a, 1024 * 1024 * 2 * i, MADV_DONTNEED); free(a);直接使用
mmap/munmap管理大内存:对于需要精确控制虚拟内存的场景,绕过tcmalloc,直接用mmap分配大内存,使用完成后调用munmap直接释放虚拟地址空间:char* a = (char*)mmap(NULL, 1024 * 1024 * 2 * i, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); // 使用内存... munmap(a, 1024 * 1024 * 2 * i);
3. 针对测试场景的优化
你的测试代码循环分配递增大小的内存,会导致tcmalloc的内存池产生大量虚拟地址碎片。可以在循环结束后,先清空线程缓存,再调用ReleaseFreeMemory(),同时配合提高release_rate,能显著减少虚拟内存占用。
内容的提问来源于stack exchange,提问作者Yale

