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

基于Linux sysinfo的C fread/C++ read函数内存占用异常问题

内存异常原因分析与解决方法

让我一步步拆解你遇到的这些内存异常问题,每个现象背后都是Linux内存管理的特性在起作用:

1. new分配数组后系统空闲内存无变化

这是Linux的**延迟内存分配(Lazy Allocation)**机制导致的:

  • 当你用new(底层调用malloc)分配内存时,操作系统只是在进程的虚拟地址空间里预留了对应的空间,但并没有真正分配物理内存。
  • 只有当你对这块内存执行写操作(比如memset)时,CPU触发页错误(Page Fault),操作系统才会真正分配物理页给你的进程。这就是为什么new之后空闲内存没变化,memset之后才会下降。

2. fread读取后内存占用接近预期值的2倍

这个问题的核心是双重内存副本,也是导致你48GB文件读取中断的原因:

  • 当你用标准库的fread读取文件时,内核会先把文件内容读到**内核页缓存(Page Cache)**里,然后再复制到你用户态的数组中。这就导致同一份数据在物理内存里存了两份:一份在页缓存,一份在你的用户态数组。
  • 你的48GB文件需要的用户态内存是48GB,加上内核页缓存的48GB,总共需要96GB物理内存,但你的系统只有91GB左右的空闲内存,最终导致内存耗尽,进程被终止。
  • 另外,fread本身还会使用用户态的缓冲区(默认的FILE流缓冲区),这也会额外占用一部分内存,但主要的内存消耗来自内核页缓存的副本。

3. 释放数组、关闭文件后仍有大量内存未被释放

这是Linux内核的页缓存回收策略导致的:

  • 关闭文件后,内核不会立即释放页缓存里的内容——它会把这些缓存保留下来,万一后续有进程需要读取同一个文件,可以直接从缓存里取,避免磁盘IO。
  • 只有当系统内存紧张时,内核才会主动回收这些页缓存,把内存释放出来。所以你看到的空闲内存没有立即恢复,是正常的内核行为。

解决方法

针对你的大文件读取场景,推荐以下几种优化方案:

方案1:使用直接IO(Direct IO)绕过内核页缓存

直接IO可以让数据直接从磁盘读到你的用户态数组,跳过内核页缓存,避免双重内存占用:

  • 替换fopen为open系统调用,指定O_DIRECT标志(注意需要对齐内存和IO块大小,通常是512字节或4KB):
    int fd = open(_file_name.c_str(), O_RDONLY | O_DIRECT);
    
  • 用read系统调用代替fread,注意内存地址和读取大小要对齐(可以用posix_memalign分配对齐的内存,代替new,因为new分配的内存不一定满足对齐要求)。

方案2:使用mmap映射文件到内存

mmap会把文件直接映射到进程的虚拟地址空间,用户态可以直接访问内核页缓存里的内容,不需要额外的用户态缓冲区:

  • 用mmap代替new和fread,这样内存里只有页缓存这一份数据,不会有用户态数组的副本,大幅减少内存占用:
    int fd = open(_file_name.c_str(), O_RDONLY);
    // 先获取文件大小
    off_t file_size = lseek(fd, 0, SEEK_END);
    lseek(fd, 0, SEEK_SET); // 重置文件指针到开头
    
    // 映射文件到内存
    void* mapped_data = mmap(NULL, file_size, PROT_READ, MAP_PRIVATE, fd, 0);
    if (mapped_data == MAP_FAILED) {
        // 处理映射失败
        close(fd);
        return false;
    }
    
    // 直接访问mapped_data里的内容,比如解析directed、vertices_count等
    bool* directed_ptr = static_cast<bool*>(mapped_data);
    bool directed = *directed_ptr;
    int* vertices_count_ptr = reinterpret_cast<int*>(directed_ptr + 1);
    int vertices_count = *vertices_count_ptr;
    // 后续直接使用映射后的内存访问数组,不需要fread
    
    // 用完后解除映射并关闭文件
    munmap(mapped_data, file_size);
    close(fd);
    

方案3:主动告知内核回收页缓存(仅用于调试,不推荐生产环境)

如果你只是想验证内存是否能被释放,可以在关闭文件后调用以下命令(需要root权限):

echo 3 > /proc/sys/vm/drop_caches

这个命令会强制内核释放所有页缓存、目录项和inodes,但频繁使用会影响系统性能,生产环境不建议这么做。


内容的提问来源于stack exchange,提问作者Ilya Afanasiev

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 06:57:58