You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

向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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 21:55:55