向std::map插入1000个文本文件缓冲区时出现内存泄漏
内存泄漏问题分析与解决
问题描述
每隔4秒向std::map插入1000个文本文件缓冲区(总大小约116MB),程序初始内存占用2MB,约50次迭代后内存增长至50-60MB且无法释放。代码如下:
namespace fs = boost::filesystem; typedef std::map<std::string, std::string> data_map_; std::mutex c_mutex; std::string readFile(const std::string &filename) { if (boost::filesystem::exists(filename)) { std::ifstream in(filename, std::ios::binary); const auto beg = in.tellg(); in.seekg(0, std::ios::end); const auto end = in.tellg(); const auto sz = end - beg; std::string buffer; if (0 != sz) { in.seekg(0, std::ios::beg); buffer.resize(sz); in.read(&buffer[0], buffer.size()); } return buffer; } else { return ""; } } void load_file(std::string &fname, data_map_ &img_data_map) { { std::lock_guard<std::mutex> lock(c_mutex); img_data_map.insert(std::make_pair(fname, readFile(fname))); } } void preparelist(std::vector<fs::path> &pathList, data_map_ &txt_map) { for (auto filePath : pathList) { std::string f_path = filePath.string(); load_file(f_path, (txt_map)); } } int main(int argc, char *argv[]) { std::string dir_path = "F:/tmp/1000_files"; // Folder size 116 MB std::vector<fs::path> pathList; std::copy(fs::recursive_directory_iterator(dir_path), fs::recursive_directory_iterator(), back_inserter(pathList)); int i = 1; while (true) { { data_map_ txt_map; preparelist(pathList, std::ref(txt_map)); txt_map.clear(); }// txt_map scope will clear. std::cout << "Images are Prepared : " << i << std::endl; i++; boost::this_thread::sleep_for(boost::chrono::seconds(4)); } }
内存未释放的原因
- STL容器的内存缓存策略:
std::map和std::string使用的默认分配器std::allocator会缓存已分配的内存块。当容器销毁或调用clear()时,仅释放元素占用的内存,但底层内存块会被保留,用于后续分配请求,避免频繁系统调用的开销。这部分内存属于进程占用,但并非真正的内存泄漏(可复用)。 - 字符串内存残留:
readFile返回的std::string在销毁时,其内存会被释放给分配器,但分配器不会立即将内存还给操作系统,而是缓存起来。 - 操作系统内存管理机制:操作系统不会主动回收进程未使用但未释放的内存,只有当系统内存紧张时才会回收这部分内存。
内存清理方案
1. 强制释放容器底层内存
通过交换空容器的方式,让原容器的内存被临时容器销毁时释放:
// 替换原代码中的txt_map.clear(); data_map_().swap(txt_map);
临时空容器与txt_map交换内存后,临时容器销毁时会将所有内存归还给分配器。
2. 使用无缓存的自定义分配器
实现一个直接释放内存到操作系统的分配器,牺牲部分性能换取严格的内存控制:
#include <malloc.h> #ifdef _WIN32 #include <windows.h> #endif template <typename T> struct NoCacheAllocator : std::allocator<T> { void deallocate(T* p, size_t n) { std::allocator<T>::deallocate(p, n); // 强制通知操作系统回收内存 #ifdef _WIN32 VirtualFree(p, n * sizeof(T), MEM_RELEASE); #else madvise(p, n * sizeof(T), MADV_DONTNEED); #endif } }; // 修改data_map_的定义 typedef std::map<std::string, std::string, std::less<std::string>, NoCacheAllocator<std::pair<const std::string, std::string>>> data_map_;
3. 操作系统层面触发内存回收
在每次迭代结束后调用系统API,强制缩小进程工作集:
#ifdef _WIN32 SetProcessWorkingSetSize(GetCurrentProcess(), (SIZE_T)-1, (SIZE_T)-1); #else malloc_trim(0); #endif
此操作仅让操作系统标记空闲内存为可回收,内存仍被分配器持有,适合观察内存使用情况,不建议频繁调用。
4. 优化数据结构
- 若无需有序存储,替换
std::map为std::unordered_map,其内存分配更灵活; - 复用缓冲区内存池,避免每次重新分配大内存块,减少内存碎片和分配开销。
内容的提问来源于stack exchange,提问作者viki
相关产品推荐
相关产品推荐

