张银奎老师的<软件调试>第23章提到可以用windbg !heap -v HeapHandle来查看堆解除提交的粒度。我在win7 32bit机器上测试系统堆解除提交的情况,得到了不同的结果。
首先我的系统堆地址是0x360000:
0:001> !heap -a
Index Address Name Debugging options enabled
1: 00360000
Segment at 00360000 to 00460000 (00028000 bytes committed)
2: 00010000 - heap headers inaccessible, skipping
3: 00020000 - heap headers inaccessible, skipping
4: 00320000
Segment at 00320000 to 00330000 (00005000 bytes committed)
系统堆解除提交的粒度是0x2000,因此0x2000*8=0x10000=65536B,即堆上有64KB的空闲内存就会释放内存。
0:001> !heap -v 0x00360000
Index Address Name Debugging options enabled
1: 00360000
Segment at 00360000 to 00460000 (00028000 bytes committed)
Flags: 00000002
ForceFlags: 00000000
Granularity: 8 bytes
Segment Reserve: 00100000
Segment Commit: 00002000
DeCommit Block Thres: 00000800
DeCommit Total Thres: 00002000
Total Free Size: 000003ad
3张图依次是分配堆块前,分配后,释放后,程序在任务管理器中显示的vm大小。
可以看到分配和释放前后,vm的大小没有改变。
这就很奇怪了,我不断尝试增加申请和释放的堆内存数量,直到分配释放的堆内存数量接近512KB时候,在任务管理器中有明显的decommit动作。
最后,我查看peb中关于堆释放的粒度总算找到了解释这个疑惑的点:
0:001> dt _peb @$peb
HiHeapVC8!_PEB
+0x080 HeapDeCommitTotalFreeThreshold : 0x10000
+0x084 HeapDeCommitFreeBlockThreshold : 0x1000
当堆上的总空闲空间达到:0x10000(颗粒度)*8(单位)=0x80000B(512KB)时,堆管理器才会立即向内存管理器执行decommit操作,真正释放内存。另外decommit释放内存不一定发生在释放512KB,在508KB的时候就会释放。我的猜测可能是因为进程堆上本来就有空闲内存,加上本次释放的内存,正好够上512KB,因此触发了释放操作