基于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
相关产品推荐
相关产品推荐

