Level 4 · Chapter 9
Pool & heap
Two entirely separate allocation worlds, serving different trust domains — and why confusing them misdirects real debugging.
Everything in this level so far has described memory in terms of large-scale regions and pages. Very little real code allocates memory a whole page at a time, though — most allocations are small, frequent, and short-lived: a string here, a small structure there. This chapter is about the two, genuinely separate allocators Windows uses to serve exactly that pattern, one for user mode and one for kernel mode.
User-mode heaps: serving application allocations
Ordinary application memory allocation — malloc, new, and the runtime library machinery underneath them — is ultimately served by the user-mode heap manager, which carves large virtual regions (obtained from the Memory Manager, tracked as VADs) into smaller, individually-manageable chunks, tracking which are free and which are in use. This is entirely a user-mode concern: the heap manager runs as ordinary code inside the process, with no special kernel privilege, and a bug in one process's heap management can't directly corrupt another process's heap, because they're operating on entirely separate address spaces to begin with.
Kernel pool: serving privileged components and drivers
The kernel needs the equivalent capability — frequent, small, efficient allocations — for its own internal data structures and for drivers, but it can't simply reuse the user-mode heap manager, because kernel-mode allocations have different requirements: they need to work correctly at various IRQLs (see Kernel mechanisms for why that constraint exists at all), and a bug in kernel pool usage has system-wide consequences a user-mode heap bug simply can't have. The kernel's own allocator for this purpose is the pool, split conceptually into paged pool (memory that can itself be paged out, usable only at low IRQL) and nonpaged pool (memory guaranteed to stay resident, usable even from contexts that can't tolerate a page fault).
Pool tags: why kernel allocations are traceable in a way user-mode ones typically aren't
A detail that matters a great deal in practice: kernel pool allocations are commonly tagged with a short, four-character identifier — a pool tag — chosen by whichever driver or component made the allocation. This isn't optional bureaucracy; it's what makes kernel memory growth diagnosable at all. When total pool usage climbs unexpectedly, tools built around pool tracking (poolmon, and equivalents built into broader diagnostic tooling) can break total usage down by tag, pointing directly at which specific driver or subsystem is responsible — turning "kernel memory usage is rising, no idea why" into "this specific tag, meaning this specific component, is growing," which is an actionable starting point rather than a dead end.
A worked example: investigating kernel memory growth
A machine showing steadily rising nonpaged pool usage, with no single process's own memory footprint explaining it, points squarely at a kernel-mode leak — most plausibly a driver failing to free something it allocated. Because that allocation almost certainly carries a pool tag, the investigation path is direct: identify which tag is growing disproportionately, map that tag back to the responsible driver (documented pool-tag lists exist for exactly this purpose, alongside driver-specific documentation), and treat that driver as the prime suspect — a workflow that has no clean equivalent for diagnosing a leak inside one specific process's user-mode heap, where the leaked allocations generally aren't tagged at all.
A common mistake
Treating "memory leak" as one undifferentiated category glosses over a real, practically important distinction: a user-mode heap leak affects exactly one process, is bounded by that process's own address space, and disappears the moment the process exits. A kernel pool leak affects the entire system, persists across every process, and can only be resolved by fixing (or unloading) whatever driver is responsible — an entirely different severity and diagnostic path, even though both get casually described with the same word.
Where this connects
- Kernel mechanisms covers the IRQL rules that shape why paged and nonpaged pool have to be different things at all.
- Working sets & trimming covers what happens to the physical pages backing paged pool under memory pressure, same as any other resident memory.