Windows堆管理
Windows的堆管理器,负责管理使用页面粒度内存分配函数保留的大内存区中的内存分配。
这句话的意思是,堆是基于页粒度内存分配的更高级的内存管理策略。
“使用页面粒度内存分配函数保留的大内存区”指的是操作系统底层的内存管理单元。在 Windows 中,虚拟内存管理器以页(Page,通常为 4KB)为基本单位管理物理内存,并且通过 VirtualAlloc等函数分配内存时,实际上是以分配粒度(Allocation Granularity,通常为 64KB) 来保留地址空间的。这意味着,即使应用程序只申请 1 字节,VirtualAlloc也会为其保留至少 64KB 的地址空间,造成巨大的浪费。
堆管理器并不直接操作物理页,而是先通过 VirtualAlloc向系统“批发”一大块连续的虚拟地址空间(称为“堆段”或“堆块”),然后在这块“批发”来的大内存区内部,自行实现一套精细的分配、释放、合并算法,将内存“零售”给应用程序。这样,多个小内存申请可以共享同一个 64KB 的保留区域,极大地提升了内存利用率和地址空间效率。
在64位系统中,Windows堆管理器分配粒度最小为16字节。
NT堆
这是最通用的堆。NT堆分为前端和后端:
堆前端层
前端分配器是性能优化层,专为高频、小内存分配设计,核心目标是极速分配(O(1) 时间复杂度) 与低碎片。
- 低碎片堆(Low-Fragmentation Heap, LFH):Windows XP SP2 后默认启用,是主要的前端实现。它将内存预切分为约 128 种固定大小的“桶”(Bucket),每个桶内块大小相同。申请时直接映射到对应桶,从空闲链表中取出第一块,几乎无计算开销。
- 适用场景:对象大小 ≤ 16KB(默认阈值)且分配频率高(默认触发阈值约为 16 次连续分配)。
堆后端层
后端分配器是基础管理层,直接与虚拟内存管理器交互,负责堆段(Segment)的生命周期管理与大对象分配。
- 堆段管理:通过 VirtualAlloc向系统申请大的连续地址空间(默认为 1MB),称为堆段。后端在其中维护空闲列表(Free Lists) 和位图(Bitmaps) 来跟踪块状态。
- 分配策略:
- 小块分配:在现有堆段中查找足够大的空闲块,可能进行分割(Split)以满足请求。
- 大块分配:当单次申请超过 512KB(默认阈值) 时,直接调用 VirtualAlloc创建独立的“大对象堆段”,绕过前端管理。
后端作用如下:
- 当前端桶耗尽时,向后端申请新内存;空闲过多时释放堆段。
- 碎片整理:合并相邻空闲块(Coalescing),减少外部碎片。
- 元数据维护:在每个内存块头部存储大小、状态(占用/空闲)等元数据。
段堆
Windows 段堆(Segment Heap)是 Windows 10 及更高版本引入的新一代堆管理器,旨在替代传统的 NT 堆,为现代应用程序(尤其是 UWP 应用、Microsoft Edge 及关键系统进程)提供更高性能、更低内存开销和更强安全性的内存分配服务。
段堆还有一个特性就是元数据分离,不像NT堆那样将元数据与实际数据混合在一起。
调试实例
0:023> dt ntdll!_heap 000001b568940000 +0x000 Segment : _HEAP_SEGMENT +0x000 Entry : _HEAP_ENTRY +0x010 SegmentSignature : 0xffeeffee +0x014 SegmentFlags : 1 +0x018 SegmentListEntry : _LIST_ENTRY [ 0x000001b5`68940120 - 0x000001b5`68940120 ] +0x028 Heap : 0x000001b5`68940000 _HEAP +0x030 BaseAddress : 0x000001b5`68940000 Void +0x038 NumberOfPages : 0x10 +0x040 FirstEntry : 0x000001b5`68940740 _HEAP_ENTRY +0x048 LastValidEntry : 0x000001b5`68950000 _HEAP_ENTRY +0x050 NumberOfUnCommittedPages : 7 +0x054 NumberOfUnCommittedRanges : 1 +0x058 SegmentAllocatorBackTraceIndex : 0 +0x05a Reserved : 0 +0x060 UCRSegmentList : _LIST_ENTRY [ 0x000001b5`68948fe0 - 0x000001b5`68948fe0 ] +0x070 Flags : 2 +0x074 ForceFlags : 0 +0x078 CompatibilityFlags : 0 +0x07c EncodeFlagMask : 0x100000 +0x080 Encoding : _HEAP_ENTRY +0x090 Interceptor : 0 +0x094 VirtualMemoryThreshold : 0xff00 +0x098 Signature : 0xeeffeeff +0x0a0 SegmentReserve : 0x100000 +0x0a8 SegmentCommit : 0x2000 +0x0b0 DeCommitFreeBlockThreshold : 0x400 +0x0b8 DeCommitTotalFreeThreshold : 0x1000 +0x0c0 TotalFreeSize : 0x256 +0x0c8 MaximumAllocationSize : 0x00007fff`fffdefff +0x0d0 ProcessHeapsListIndex : 0 +0x0d2 HeaderValidateLength : 0x2c0 +0x0d8 HeaderValidateCopy : (null) +0x0e0 NextAvailableTagIndex : 0 +0x0e2 MaximumTagIndex : 0 +0x0e8 TagEntries : (null) +0x0f0 UCRList : _LIST_ENTRY [ 0x000001b5`68948fd0 - 0x000001b5`68948fd0 ] +0x100 AlignRound : 0x1f +0x108 AlignMask : 0xffffffff`fffffff0 +0x110 VirtualAllocdBlocks : _LIST_ENTRY [ 0x000001b5`68940110 - 0x000001b5`68940110 ] +0x120 SegmentList : _LIST_ENTRY [ 0x000001b5`68940018 - 0x000001b5`68940018 ] +0x130 AllocatorBackTraceIndex : 0 +0x134 NonDedicatedListLength : 0 +0x138 BlocksIndex : 0x000001b5`689402e8 Void +0x140 UCRIndex : (null) +0x148 PseudoTagEntries : (null) +0x150 FreeLists : _LIST_ENTRY [ 0x000001b5`689407e0 - 0x000001b5`68946af0 ] +0x160 LockVariable : 0x000001b5`689402c0 _HEAP_LOCK +0x168 CommitRoutine : 0x76f7da56`aaec3876 long +76f7da56aaec3876 +0x170 StackTraceInitVar : _RTL_RUN_ONCE +0x178 CommitLimitData : _RTLP_HEAP_COMMIT_LIMIT_DATA +0x188 UserContext : 0x000001b5`69113d80 Void +0x190 Spare : 0 +0x198 FrontEndHeap : 0x000001b5`68ba0000 Void +0x1a0 FrontHeapLockCount : 0 +0x1a2 FrontEndHeapType : 0x2 '' +0x1a3 RequestedFrontEndHeapType : 0x2 '' +0x1a8 FrontEndHeapUsageData : 0x000001b5`68941210 -> 0 +0x1b0 FrontEndHeapMaximumIndex : 0x402 +0x1b2 FrontEndHeapStatusBitmap : [129] "" +0x233 ReadOnly : 0y1 +0x233 InternalFlags : 0x1 '' +0x238 Counters : _HEAP_COUNTERS +0x2b0 TuningParameters : _HEAP_TUNING_PARAMETERS