首页
社区
课程
招聘
[原创]内存都去哪了?探究 VirtualAlloc 分配背后被“浪费”的 60KB
发表于: 2025-12-13 14:34 539

[原创]内存都去哪了?探究 VirtualAlloc 分配背后被“浪费”的 60KB

2025-12-13 14:34
539

摘要

本文记录了一次因 VirtualAlloc 分配失败引发的 OOM 问题排查过程。通过编写测试程序模拟内存分配,发现 32 位进程在未开启 Large Address Aware 时,用户空间仅 2GB 可用,且 VirtualAlloc 实际分配粒度均为 64KB——若申请 4KB,剩余 60KB 将变为不可用空间,导致地址空间碎片化与大量浪费。借助 VMMap 碎片视图与 Windbg 分析,直观展示了分配粒度对内存布局的影响,并验证了按 64KB 对齐分配可避免该问题。

测试程序

测试代码非常简单,我就直接贴到这里了,如下:

#include "stdafx.h"
#include "stdio.h"
#include "windows.h"

int main(int argc, char *argv[])
{
 int nk = 4;
 if (argc < 2)
 {
   printf("usage: TestVirtualAlloc.exe N(kb). default 4kb");
 }
 else
 {
   nk = atoi(argv[1]);
 }

   int idx = 0;
   while (true)
   {
       auto pAddr = VirtualAlloc(nullptr, nk * 1024, MEM_RESERVE, PAGE_READWRITE);
       if (pAddr == nullptr)
       {
     printf("VirtualAlloc(%dkb) loop %6d failed. last error 0n%d\r\n", nk, idx++, GetLastError());
           getchar();
       }
       else
       {
     printf("VirtualAlloc(%dkb) loop %6d succeed. address 0x%08x\r\n", nk, idx++, pAddr);
       }
   }
}

Free 空间出乎意料的大

编译 32 位版本的程序,执行 TestVirtualAlloc.exe 4(每次分配 4kb),执行一段时间后,最终会失败,如下图:

virtual-alloc-failed

这是预料内的现象,因为程序在不停的分配空间,32 位进程的虚拟地址空间仅有 4GB,在未开启 Large Address Aware 的情况下,用户程序可用的空间仅有 2GB 空间可用。

用 windbg 附加到该进程,执行 !address -summary 查看地址空间情况,发现 Free 占比有点太大了(88.90%),这不符合预期。程序在不停的分配空间,虽然没有 commit,但是已经 reserve 了,不应该有这么多 Free 空间才对。

address-summary

VMMap 是查看虚拟内存空间的神器,可以非常详细的查看某个进程的内存空间布局。何不用 VMMap 查看一下?

请出 VMMap

打开 VMMap 并选择 TestVirtualAlloc.exe,查看其虚拟内存空间。

vmmap-view-memory

说明:切记勾选 Options 选项下的 Show Free and Unsuable Regions

当我看到 60 K 的 Unusable 跟在 4 K 的 Private Data 后时,沉睡的记忆终于被唤醒了,VirtualAlloc 的分配粒度是 64kb !!!

在 windbg 中使用 !address 命令查看地址 0x00490000 和 0x00491000 的情况,如下图:

windbg-address-490000-491000

地址 0x00491000 所属的区域是 MEM_FREE 的。需要注意的是虽然从 0x00491000 开始的 60kb 是 Free 的,但是这块地址不能被分配使用了,这就是为什么 VMMap 中显示为 Unusable 的原因。

如果我分配的是 64kb,那么就不会有这么大的 Free 空间了。是不是呢?简单验证一下就知道了。

继续验证

执行 TestVirtualAlloc.exe 64(每次会分配 64kb),然后使用 windbg 观察内存空间的情况。

address-summary-no-much-free-memory

可以发现 MEM_FREE 类型的内存空间很小了,基本上全是 MEM_RESERVE 类型的内存空间。使用 VMMap 查看的话,也差不多,这里就不截图了。感兴趣的小伙伴可以自己动手实验。

疑问

折腾完,我突然意识到一个致命问题 —— 我的系统是 64 位的啊。32 位程序在 64 位操作系统下,用户态内存空间应该是 4GB 才对,这里为啥才 2GB 呢?想必聪明的你也一定知道其中的缘由了,想要使用 4GB 的内存空间,必须开启 Large Address Aware 才行。

经过确认,编译程序的时候,默认是没开启这个选项的。

enable-large-address-aware

开启后,再次执行程序,可以发现可分配的内存地址是大于 2gb 的,接近 4gb

allocate-at-address-higher-than-2gb

Fragmentation View

其实,VMMap 还有一个非常强悍的功能,叫 Fragmentation View。此功能可以鸟瞰进程的整个内存空间,也可以放大到对应的区域查看,点击对应区域还可以查看具体的地址范围。从下图可以很明显的看到每次分配 4kb 的内存分布模式 —— 4kb 的 reserve 空间(黄色方块)后跟着 60kb 的 Unusable 空间(灰色区域)。

vmmap-fragment-view

亲自动手

我已经把对应的示例代码上传到了 github,感兴趣的小伙伴可以自行实验。

总结

  • VMMap 是查看虚拟内存空间的神兵利器
  • VirtualAlloc 分配粒度是 64kb,如果分配的过小,会产生浪费,甚至是内存碎片
  • 开启 Large Address Aware 后,32 位程序才能使用更大的内存空间



传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 2
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回