32位Windows服务中MapViewOfFile返回ERROR_NOT_ENOUGH_MEMORY错误求助
问题描述
我有一个运行在Windows Server 2012 R2 Standard系统上的32位服务程序,采用客户端-服务器架构,每个新客户端连接时会调用MapViewOfFile() API映射文件视图,每次创建的视图大小为sizeof(CLRHostArea_t)(约8MB)。运行约190次客户端连接请求后,服务器崩溃,原因是MapViewOfFile()返回NULL,GetLastError()返回错误码8(ERROR_NOT_ENOUGH_MEMORY),后续所有调用都会返回该错误。
服务器核心代码片段
创建文件映射句柄:
hAISHostMap = CreateFileMappingA(INVALID_HANDLE_VALUE, &Sa, PAGE_READWRITE , 0, sizeof(CLRHostArea_t), FileMapName);
映射文件视图:
this->CLRHostArea = (CLRHostArea_t*)MapViewOfFile(hAISHostMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);
复现测试程序
为复现问题编写的测试程序同样出现错误:
const int sizeOfTheFileMappingObject = 8394264; const int numberOfBytesToMap = sizeOfTheFileMappingObject; const char* fileMappingObjectName = "Global\\QWERTY"; void AllocateMemoryWithMalloc(std::vector<void*>* addresses) { const size_t sizeOfMemAllocatedAtOnce = 32*1024; for (;;) { void* address = malloc(sizeOfMemAllocatedAtOnce); if (address != NULL) { addresses->push_back(address); } else { printf("The %dth malloc() returned NULL. Allocated memory: %d MB\n", addresses->size() + 1, (addresses->size() * sizeOfMemAllocatedAtOnce) / (1024 * 1024)); break; } } } void DeallocateMemoryWithFree(std::vector<void*>* addresses) { std::vector<void*>::iterator current = addresses->begin(); std::vector<void*>::iterator end = addresses->end(); for (; current != end; ++current) { free(*current); } addresses->clear(); printf("Memory is deallocated.\n"); } void TryToMapViewOfFile() { HANDLE fileMapping = OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, fileMappingObjectName); if (fileMapping == NULL) { printf("OpenFileMapping() failed. LastError: %d\n", GetLastError()); return; } LPVOID mappedView = MapViewOfFile(fileMapping, FILE_MAP_READ, 0, 0, numberOfBytesToMap); if (mappedView == NULL) { printf("MapViewOfFile() failed. LastError: %d\n", GetLastError()); if (!CloseHandle(fileMapping)) { printf("CloseHandle() failed. LastError: %d\n", GetLastError()); } return; } if (!UnmapViewOfFile(mappedView)) { printf("UnmapViewOfFile() failed. LastError: %d\n", GetLastError()); if (!CloseHandle(fileMapping)) { printf("CloseHandle() failed. LastError: %d\n", GetLastError()); } return; } if (!CloseHandle(fileMapping)) { printf("CloseHandle() failed. LastError: %d\n", GetLastError()); return; } printf("MapViewOfFile() succeeded.\n"); } int main(int argc, char* argv[]) { HANDLE fileMapping = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeOfTheFileMappingObject, fileMappingObjectName); if (fileMapping == NULL) { printf("CreateFileMapping() failed. LastError: %d\n", GetLastError()); return -1; } TryToMapViewOfFile(); std::vector<void*> addresses; AllocateMemoryWithMalloc(&addresses); TryToMapViewOfFile(); DeallocateMemoryWithFree(&addresses); TryToMapViewOfFile(); if (!CloseHandle(fileMapping)) { printf("CloseHandle() failed. LastError: %d\n", GetLastError()); } return 0; }
测试现象:
- 64位平台:即使不释放内存,
TryToMapViewOfFile()每次调用都成功 - 32位平台:调用
AllocateMemoryWithMalloc()后,即使调用DeallocateMemoryWithFree()释放内存,TryToMapViewOfFile()仍失败,GetLastError()返回错误码8
疑问:这是否是因为32位环境下即使调用UnmapViewOfFile()和CloseHandle()也无法正确释放内存?需要32位环境下的解决方案。
问题原因分析
并非UnmapViewOfFile()或CloseHandle()未正确释放内存,核心问题是32位进程的虚拟地址空间碎片:
- 32位Windows进程默认仅拥有2GB用户态虚拟地址空间(开启4GT后为3GB),每次映射8MB的连续内存块,频繁分配释放小内存后,地址空间会产生大量碎片,导致无法找到足够大的连续空闲块来映射8MB的文件视图——即使总空闲内存足够。
MapViewOfFile()默认会自动寻找合适的连续地址块,当碎片过多时,就会返回ERROR_NOT_ENOUGH_MEMORY。
32位环境下的解决方案
1. 预先保留连续虚拟地址空间
在进程启动时,预先保留一块足够大的连续虚拟地址空间,后续映射文件视图时指定该空间的地址,从根源避免碎片问题:
- 使用
VirtualAlloc()以MEM_RESERVE参数保留地址空间(不提交物理内存) - 映射视图时,用
MapViewOfFileEx()替代MapViewOfFile(),指定预先保留的地址
示例代码片段:
// 进程启动时执行:预留200个8MB块的连续空间 LPVOID reservedAddr = VirtualAlloc(NULL, 8*1024*1024*200, MEM_RESERVE, PAGE_NOACCESS); // 映射视图时使用(offset按8MB对齐,每次映射后更新) this->CLRHostArea = (CLRHostArea_t*)MapViewOfFileEx(hAISHostMap, FILE_MAP_ALL_ACCESS, 0, 0, 0, reservedAddr + offset);
2. 复用文件映射视图
不为每个客户端创建新的映射视图,而是复用同一个视图:
- 客户端断开时,仅重置视图内的数据,而非卸载映射再重新映射
- 避免频繁的映射/卸载操作加剧地址空间碎片
3. 启用4GT(4GB Tuning)
修改系统配置,让32位进程能使用3GB的用户态虚拟地址空间:
- 用BCDEdit工具修改启动配置,添加
/3GB参数 - 程序编译时设置
/LARGEADDRESSAWARE链接选项,让进程支持访问超过2GB的地址空间
- 此方法直接增加可用地址空间,降低碎片导致的分配失败概率
4. 拆分大映射块
将8MB的CLRHostArea_t拆分为多个小的、独立的文件映射对象,每个对象大小控制在1MB或更小,这样即使地址空间有碎片,也更容易找到连续块来映射小内存块
5. 优化内存分配策略
- 避免频繁分配释放小内存块,改用内存池技术,复用已分配的小内存,减少地址空间碎片
- 使用
_aligned_malloc分配对齐的内存,降低碎片产生的概率
内容的提问来源于stack exchange,提问作者saim
相关产品推荐
相关产品推荐

