摘要
本文记录了一次因 VirtualAlloc 分配失败引发的 OOM 问题排查过程。通过编写测试程序模拟内存分配,发现 32 位进程在未开启 Large Address Aware 时,用户空间仅 2GB 可用,且 VirtualAlloc 实际分配粒度均为 64KB——若申请 4KB,剩余 60KB 将变为不可用空间,导致地址空间碎片化与大量浪费。借助 VMMap 碎片视图与 Windbg 分析,直观展示了分配粒度对内存布局的影响,并验证了按 64KB 对齐分配可避免该问题。
测试程序
测试代码非常简单,我就直接贴到这里了,如下:
1 |
|
Free 空间出乎意料的大
编译 32 位版本的程序,执行 TestVirtualAlloc.exe 4(每次分配 4kb),执行一段时间后,最终会失败,如下图:

这是预料内的现象,因为程序在不停的分配空间,32 位进程的虚拟地址空间仅有 4GB,在未开启 Large Address Aware 的情况下,用户程序可用的空间仅有 2GB 空间可用。
用 windbg 附加到该进程,执行 !address -summary 查看地址空间情况,发现 Free 占比有点太大了(88.90%),这不符合预期。程序在不停的分配空间,虽然没有 commit,但是已经 reserve 了,不应该有这么多 Free 空间才对。

VMMap 是查看虚拟内存空间的神器,可以非常详细的查看某个进程的内存空间布局。何不用 VMMap 查看一下?
请出 VMMap
打开 VMMap 并选择 TestVirtualAlloc.exe,查看其虚拟内存空间。

说明:切记勾选
Options选项下的Show Free and Unsuable Regions
当我看到 60 K 的 Unusable 跟在 4 K 的 Private Data 后时,沉睡的记忆终于被唤醒了,VirtualAlloc 的分配粒度是 64kb !!!
在 windbg 中使用 !address 命令查看地址 0x00490000 和 0x00491000 的情况,如下图:

地址 0x00491000 所属的区域是 MEM_FREE 的。需要注意的是虽然从 0x00491000 开始的 60kb 是 Free 的,但是这块地址不能被分配使用了,这就是为什么 VMMap 中显示为 Unusable 的原因。
如果我分配的是 64kb,那么就不会有这么大的 Free 空间了。是不是呢?简单验证一下就知道了。
继续验证
执行 TestVirtualAlloc.exe 64(每次会分配 64kb),然后使用 windbg 观察内存空间的情况。
可以发现 MEM_FREE 类型的内存空间很小了,基本上全是 MEM_RESERVE 类型的内存空间。使用 VMMap 查看的话,也差不多,这里就不截图了。感兴趣的小伙伴可以自己动手实验。
疑问
折腾完,我突然意识到一个致命问题 —— 我的系统是 64 位的啊。32 位程序在 64 位操作系统下,用户态内存空间应该是 4GB 才对,这里为啥才 2GB 呢?想必聪明的你也一定知道其中的缘由了,想要使用 4GB 的内存空间,必须开启 Large Address Aware 才行。
经过确认,编译程序的时候,默认是没开启这个选项的。

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

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

亲自动手
我已经把对应的示例代码上传到了 github,感兴趣的小伙伴可以自行实验。
总结
VMMap是查看虚拟内存空间的神兵利器VirtualAlloc分配粒度是64kb,如果分配的过小,会产生浪费,甚至是内存碎片- 开启
Large Address Aware后,32位程序才能使用更大的内存空间