在VMMap中跟踪不可用的虚拟内存
VMMap是一个很好的系统内部工具,它可以可视化特定进程的虚拟内存,并帮助理解内存的用途。它有线程堆栈、映像、Win32堆和GC堆的特定报告。有时,VMMap会报告不可用的虚拟内存,这与可用内存不同。下面是32位进程(总共有2GB虚拟内存)的VMMap报告示例:
这种“不可用”的内存从何而来,为什么不能使用?Windows虚拟内存管理器具有64KB的分配粒度。当直接用VirtualAlloc分配内存并要求小于64KB(比如16KB)时,VirtualAlloc返回64KB边界上的地址。然后分配前四页(16KB),其余48KB标记为未使用。无法通过执行另一个分配来获取此内存,因为VirtualAlloc将始终返回驻留在64KB边界上的地址。那么,这个内存是不可用的。
VMMap中的fragmentation视图使问题更加明显。在下面的屏幕截图中,黄点是4KB区域,可以分配和使用,而灰色矩形是60KB区域,不能使用。当整个地址空间都是这些不可用的区域时,你得到的虚拟内存就没有你想象的那么多了。
幸运的是,很容易找到违规分配的来源。本质上,我们正在寻找分配大小小于64KB(或者更好:不能被64KB平均整除)的VirtualAlloc调用。您可以使用VMMap本身(它具有跟踪模式)跟踪这些分配,也可以附加WinDbg并设置断点:
0:000> bm kernelbase!VirtualAlloc* "r $t0 = poi(@esp+8); .if (@$t0 % 0x10000 != 0) { .printf \"Unusable memory will emerge after allocating %d bytes\", @$t0; kb 4 } .else { gc }" 1: 76c03e8a @!"KERNELBASE!VirtualAllocExNuma" 2: 76bcd532 @!"KERNELBASE!VirtualAlloc" 3: 76c03e66 @!"KERNELBASE!VirtualAllocEx" 0:000> g Unusable memory will emerge after allocating 4096 bytes ChildEBP RetAddr Args to Child 00defd48 010a4e34 00000000 00001000 00003000 KERNELBASE!VirtualAlloc 00defe2c 010a5f25 00000000 00000000 7eaa7000 FourKBLeak!allocate_small+0x34 00deff18 010a6989 00000001 012ba870 012bae98 FourKBLeak!main+0x35 00deff68 010a6b7d 00deff7c 7529919f 7eaa7000 FourKBLeak!__tmainCRTStartup+0x199
此断点确保传递给VirtualAlloc的分配大小可被64KB整除。否则,断点将停止并打印出有问题的分配和调用堆栈;否则,它将继续执行。这将使捕获小分配的源变得非常容易,并有望修复它们。