winapi - MapViewOfFile() 在进程达到 2GB 限制后不再起作用

标签 winapi memory out-of-memory

如果我们的进程还没有达到 2GB 的限制,MapViewOfFile() 可以正常工作。但是,如果进程达到限制,则即使释放了部分或全部内存,MapViewOfFile() 也不再起作用。 GetLastError() 返回 8,这意味着 ERROR_NOT_ENOUGH_MEMORY, Not enough storage is available to process this command .这是一个显示问题的小程序:

#include <Windows.h>
#include <cstdio>
#include <vector>

const int sizeOfTheFileMappingObject = 20;
const int numberOfBytesToMap = sizeOfTheFileMappingObject;
const char* fileMappingObjectName = "Global\\QWERTY";

void Allocate2GBMemoryWithMalloc(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;

    Allocate2GBMemoryWithMalloc(&addresses);

    TryToMapViewOfFile();

    DeallocateMemoryWithFree(&addresses);

    TryToMapViewOfFile();

    Allocate2GBMemoryWithMalloc(&addresses);

    DeallocateMemoryWithFree(&addresses);

    if (!CloseHandle(fileMapping)) {
        printf("CloseHandle() failed. LastError: %d\n", GetLastError());
    }

    return 0;
}

程序的输出:
MapViewOfFile() succeeded.
The 65126th malloc() returned NULL. Allocated memory: 2035 MB
MapViewOfFile() failed. LastError: 8
Memory is deallocated.
MapViewOfFile() failed. LastError: 8
The 64783th malloc() returned NULL. Allocated memory: 2024 MB
Memory is deallocated.

正如您所看到的,即使在释放所有分配的内存后,MapViewOfFile() 也失败了 8。即使 MapViewOfFile() 报告 ERROR_NOT_ENOUGH_MEMORY我们可以成功调用 malloc()。

我们在 Windows7,32bit 上运行了这个示例程序; Windows 8.1,32bit 和 Windows Server 2008 R2,64bit 结果是一样的。

所以问题是:为什么 MapViewOfFile() 失败并显示 ERROR_NOT_ENOUGH_MEMORY在进程达到 2GB 限制后?

最佳答案

为什么 MapViewOfFile 失败

正如 IInspectable 的评论所解释的那样,释放用 malloc 分配的内存并不能使其可用于 MapViewOfFile。 Windows 下的 32 位进程具有 4 GB 的虚拟地址空间,并且只有其中的前 2 GB 可用于应用程序。 (一个异常(exception)是 large address aware 程序,它在适当配置的 32 位内核下将其增加到 3 GB,在 64 位内核下增加到 4 GB。)程序内存中的所有内容都必须在前 2 GB 的某处有一个位置虚拟地址空间。这包括可执行文件本身、程序使用的任何 DLL、内存中的任何数据,无论是静态分配、堆栈分配还是动态分配(例如,使用 malloc),当然还有您使用 MapViewOfFile 映射到内存中的任何文件。

当您的程序第一次启动时,Visual C/C++ 运行时会为 malloc 和 operator new 等函数的动态分配创建一个小堆。根据需要,运行时会增加内存中堆的大小,并且这样做会占用更多的虚拟地址空间。不幸的是,它永远不会缩小堆的大小。当您释放一大块内存时,运行时只会取消提交使用的内存。这使得被释放内存使用的 RAM 可供使用,但被释放内存占用的虚拟地址空间仍然作为堆的一部分分配。

如前所述,由 MapViewOfFile 映射到内存的文件也占用虚拟地址空间。如果堆(加上你的程序、DLL 和其他所有东西)都用完了所有的虚拟地址空间,那么就没有空间来映射文件了。

一个可能的解决方案:不要使用 malloc

避免堆增长以填满所有虚拟地址空间的一种简单方法是不使用 Visual C/C++ 运行时分配大(至少 64k)内存块。而是直接使用 VirtualAlloc(..., MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE) 从 Windows 分配和释放内存和 VirtualFree(..., MEM_RELEASE) .后面的函数释放区域使用的 RAM 和它占用的虚拟地址空间,使其可用于 MapViewOfFile。

但...

您仍然可能遇到另一个问题,即使 View 的大小比可用虚拟地址空间的总量小,有时甚至小得多,MapViewOfFile 仍然会失败。这是因为 View 需要映射到虚拟地址空间的连续区域。如果虚拟地址空间变得碎片化。未保留虚拟地址空间的最大连续区域可以相对较小。即使在您的程序第一次启动时,在您有机会进行任何动态分配之前,虚拟地址空间也可能会因为 DLL 加载到不同地址而有些碎片化。如果您有一个使用 VirtualAlloc 和 VirtualFree 进行大量分配和解除分配的长期存在的程序,您最终可能会得到一个非常碎片化的虚拟地址空间。如果您遇到此问题,则必须更改分配模式,甚至可能实现您自己的堆分配器。

关于winapi - MapViewOfFile() 在进程达到 2GB 限制后不再起作用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30706809/

相关文章:

winapi - 调优套接字连接调用超时

c++ - RAWINPUT 奇怪的行为

c++ - 如何覆盖 Windows 键盘快捷键?

c# - 如何清除内存缓存?

c++ - 将函数传递给模板

从列表/字典中删除项目后,Python "sys.getsizeof"报告大小相同?

mysql - mysql查询后释放内存

C# 线程池 - OutOfMemoryException

java - 12GB Ram 机器的 Pentaho OutOfMemory 异常

python - np.hstack() 中的内存错误