Overview
What this concept solves
The slab allocator answers a different question from the fit strategies. They ask 'where do I put an arbitrary-sized request?' Slab allocation asks 'I allocate millions of objects of the same few types — how do I make that nearly free?' Its answer: give every object type its own cache of memory carved into slots exactly the right size, so allocation never searches and never wastes space on rounding.
Each cache owns one or more slabs — a slab is a chunk of contiguous memory (typically a page or a few pages from the underlying page allocator) sliced into a fixed number of equal slots. To allocate an object, you grab any free slot in the cache; to free it, you mark the slot free. There's no size matching, no splitting, no coalescing — just a free slot to claim. That's why allocation and free are O(1).
Because every slot in a cache is the exact size of the object, there is essentially no external fragmentation within a cache, and internal fragmentation is limited to the small, fixed slack the object type itself defines. Bonwick designed the slab allocator for Solaris precisely to serve the kernel's torrent of same-typed objects — inodes, file structures, task structs — and the design (and its descendants SLAB/SLUB/SLOB) is now standard in Linux and the BSDs.
Mechanics
How it works
Caches, slabs, and slots
- Cache — one per object type (e.g.
inode_cache). It knows the object size and how many slots fit in a slab, and it tracks its slabs by state. - Slab — a contiguous run of memory from the page allocator, divided into N fixed-size slots. A slab is empty (all slots free), partial (some used), or full (all used).
- Slot — space for exactly one object. Allocation claims a free slot; free releases one.
Allocate: prefer a partial slab
- Look for a partial slab first. It already has both live objects and free slots, so using it keeps memory dense and avoids touching empty slabs.
- Else use an empty slab if one is on hand (kept around for fast reuse).
- Else grow. Ask the page allocator for a fresh slab (one or more pages), slice it into slots — it starts empty — and use it.
- Claim a free slot in O(1) and update the slab's state (empty→partial, or partial→full).
Free: release and maybe reclaim
- Find the object's slot and mark it free; the slab transitions full→partial or partial→empty.
- Reclaim empty slabs. When a slab becomes fully empty, its memory can be returned to the page allocator. (Real allocators often keep a few empties cached for fast reuse instead of reclaiming immediately.)
Object caching: the other half of Bonwick's idea
The original slab design also caches constructed objects: when an object is freed, it can be kept in an initialized state so the next allocation skips re-initialization. For complex kernel structures with expensive constructors, reusing a warm, already-initialized object is a real speedup — allocation becomes 'hand back a ready-made object,' not 'find memory and build one.'
Layered on the page/buddy allocator
Slab allocation doesn't get memory from thin air — it requests whole pages from the underlying page allocator (a buddy system in Linux) and subdivides them into slots. Buddy handles coarse, power-of-two page allocation with fast coalescing; slab handles fine-grained, same-sized objects with O(1) speed. They're complementary layers, not competitors.
Interactive prototype
Run it. Break it. Tune it.
Sandboxed simulation embedded right in the page. No setup, no install.
About this simulation
A slab allocator gives each object type its own cache. A cache is a set of slabs — contiguous memory pre-divided into fixed-size slots sized exactly for that object. Allocation is just 'find a free slot,' an O(1) operation with no size search and no external fragmentation. Slabs move between empty → partial → full as slots fill, and empty slabs are reclaimed. Scenario 1 · Two object types runs two caches side by side; scenario 2 · Grow & reclaim shows a slab created, filled, and returned to the OS.
Hands-on
Try these on your own
Open the prototype above, run each experiment, predict the answer, then verify.
Allocate from two caches at once
On scenario 1 · Two object types, step through. file_cache (64B, 6 slots/slab) and task_cache (128B, 4 slots/slab) fill independently — each object goes into a slot of its own cache. Watch a new slab get created the first time each cache is touched, and note the 'External frag = 0B' stat holding steady: every slot is exactly object-sized.
Watch slab states and reclamation
Continue scenario 1 into the free operations. Freeing F3 turns its slab partial; freeing T1 then T2 empties the task slab entirely — watch it transition to EMPTY and get reclaimed back to the OS, dropping the 'Slabs in use' and 'Memory committed' stats. Then the final file allocation reuses the freed F3 slot rather than growing.
Grow past one slab, then reclaim a whole slab
Switch to scenario 2 · Grow & reclaim. Allocate seven file objects: the first six fill slab 1 (empty→partial→full), and the seventh forces a second slab to be created. Then free F1–F6 to empty slab 1 completely and watch it get reclaimed while slab 2 keeps F7. Use Auto to watch the empty→partial→full→reclaim lifecycle play out.
In practice
When to use it — and what you give up
When slab allocation is the right tool
- You allocate huge numbers of identically-sized objects — kernel objects, network buffers, tree/list nodes, connection structs. This is the workload slab was built for.
- Allocation must be O(1) and predictable — no scan, no fit search, no fragmentation-driven latency spikes; ideal for kernels and real-time-ish paths.
- Object initialization is expensive — caching constructed objects amortizes the setup cost across allocations.
- Not for arbitrary-sized requests — slab needs to know object sizes up front. General
mallocworkloads with wildly varying sizes are served by sized caches plus a fallback, not by a single slab cache.
Real-world example
The Linux kernel uses slab-style allocation (the SLUB allocator by default, with SLAB and SLOB as alternatives) for nearly all internal objects. kmalloc is backed by a set of caches for common sizes; dedicated caches like dentry, inode_cache, and task_struct serve specific object types. You can watch them live in /proc/slabinfo. FreeBSD uses a slab allocator (uma, the zone allocator) descended from the same Solaris design.
Pros
- O(1) allocate and free — just claim or release a slot; no search, no splitting.
- Almost no fragmentation — slots are exactly object-sized, so no external fragmentation within a cache.
- Object caching — freed objects can stay initialized, skipping re-construction on reuse.
- Great cache/TLB behavior — same-typed objects packed densely in slabs improve locality.
Cons
- One cache per object type — you must know sizes up front; not for arbitrary-sized allocation.
- Per-cache overhead — many object types means many caches and some bookkeeping.
- Partially-full slabs hold memory — a slab with one live object still occupies its whole page until that object frees.
- Complexity — slab states, reclamation policy, and per-CPU caches (in SLUB) add machinery.
Reference
Code & further reading
A minimal reference implementation and pointers worth bookmarking.
interface Slab { slots: (string | null)[]; } // null = free slot
class Cache {
slabs: Slab[] = [];
constructor(public objectSize: number, public slotsPerSlab: number) {}
private state(s: Slab) {
const used = s.slots.filter(Boolean).length;
return used === 0 ? "empty" : used === s.slots.length ? "full" : "partial";
}
/** O(1): claim a free slot, preferring a partial slab; grow only if needed. */
alloc(objId: string): void {
let slab = this.slabs.find((s) => this.state(s) === "partial")
?? this.slabs.find((s) => this.state(s) === "empty");
if (!slab) { // grow: new slab from page allocator
slab = { slots: Array(this.slotsPerSlab).fill(null) };
this.slabs.push(slab);
}
slab.slots[slab.slots.indexOf(null)] = objId; // take the first free slot
}
/** O(1): release the slot; reclaim the slab if it becomes empty. */
free(objId: string): void {
for (const slab of this.slabs) {
const i = slab.slots.indexOf(objId);
if (i !== -1) {
slab.slots[i] = null;
if (this.state(slab) === "empty") { // return memory to the OS
this.slabs = this.slabs.filter((s) => s !== slab);
}
return;
}
}
}
}References & further reading
5 sources- Paperusenix.org
Bonwick — The Slab Allocator: An Object-Caching Kernel Memory Allocator (USENIX 1994)
The original paper that introduced slab allocation in Solaris — readable and foundational.
- Articleen.wikipedia.org
Slab allocation — Wikipedia
Concise overview of caches, slabs, slots, and the empty/partial/full states.
- Bookkernel.org
Understanding the Linux VMM (Mel Gorman) — Ch. 8: Slab Allocator
Detailed, free walkthrough of caches, slabs, object coloring, and the slab lists.
- Docsdocs.kernel.org
Linux kernel — Short users guide for SLUB
Official docs for Linux's default slab allocator (SLUB) — tuning, debugging, and how it differs from the classic SLAB.
- Docsman7.org
slabinfo(5) — Linux manual page
What
/proc/slabinforeports — objsize, objperslab, pagesperslab. Inspect live caches on any running Linux box.
Knowledge check
Did the prototype land?
Quick questions, answers revealed on submit. No scoring saved.
question 01 / 03
Why is slab allocation O(1) with essentially no external fragmentation?
question 02 / 03
When allocating, which slab does the allocator prefer to use?
question 03 / 03
What is a key limitation of slab allocation?
0/3 answered
Was this concept helpful?
Tell us what worked, or what to improve. We read every note.