How Python Memory Arenas Allocate Memory via malloc
Python manages small-object memory through a specialized allocator
within CPython called PyMalloc, which organizes memory hierarchically
into arenas, pools, and blocks to minimize overhead. At the top of this
hierarchy sits the arena—a large, contiguous chunk of memory (typically
256 KiB) requested directly from the operating system using standard C
library calls such as malloc or virtual memory mappings.
This article explains how Python's arena mechanism requests system
memory via malloc, how it subdivides that memory to handle
runtime objects, and how it eventually releases that memory back to the
operating system.
The CPython Memory Hierarchy
CPython splits memory management into distinct layers depending on the size of the allocation request:
- Large allocations (> 512 bytes): Python bypasses
its internal custom allocator and delegates directly to the standard C
library's
malloc(orPyMem_RawMalloc). - Small allocations (≤ 512 bytes): Python routes
requests to PyMalloc. PyMalloc serves these allocations out of
pre-allocated chunks arranged in three levels:
- Blocks: The smallest unit, ranging from 8 to 512 bytes in increments of 8 bytes.
- Pools: 4 KiB memory pages containing uniform-sized blocks.
- Arenas: 256 KiB structures containing 64 pools each.
Requesting System Memory via Arenas
When Python requires memory for small objects and no existing pool has available space, PyMalloc requests a new arena.
The arena allocator relies on an underlying structure defined in
CPython's source code (obmalloc.c) as
struct arena_object. The allocation process proceeds as
follows:
- Arena Object Tracking: Python maintains an array of
arena_objectdescriptors. If an unused descriptor is unavailable, Python expands the descriptor table using standard system allocation. - Invoking the System Allocator: To back the arena
descriptor with actual contiguous memory, Python calls
_PyObject_Arena.alloc, which resolves to standard system calls. On modern POSIX systems, this can bemmapfor anonymous memory, but historically and on many platforms, it uses standard Cmalloc():p = malloc(ARENA_SIZE); /* ARENA_SIZE is typically 256 KiB (262,144 bytes) */ - Address Alignment: Pools inside an arena must be
strictly aligned to pool-size boundaries (4 KiB) for PyMalloc to compute
pool addresses quickly using bitwise masking. Because standard
mallocdoes not guarantee 4 KiB alignment, PyMalloc either allocates slightly more memory to align the base pointer manually or uses platform-specific aligned allocators (such asposix_memalignorVirtualAllocon Windows).
Arena States and Management
Arenas are managed through a doubly-linked list sorted by the number of available free pools. An arena exists in one of three states:
- Unallocated: The
arena_objectdescriptor exists, but no system memory is currently mapped to it. - Partially Allocated: The arena holds active objects, but still has free pools available for future allocations.
- Full: All 64 pools inside the 256 KiB arena are occupied.
Python prioritizes filling partially used arenas before drawing pools from completely empty arenas. This strategy minimizes fragmentation and keeps total system memory usage low.
Releasing Memory Back to the Operating System
Because PyMalloc acquires memory from the system in large 256 KiB
chunks, individual object deallocations (using free()
semantics) do not immediately return memory to the OS.
Instead, when an object is destroyed:
- The corresponding block inside a 4 KiB pool is marked as free.
- When all blocks inside a pool become free, the entire pool is marked as free within its parent arena.
- Only when all 64 pools inside an arena become completely empty can Python deallocate the entire 256 KiB block.
At that point, Python invokes standard free() (or
munmap) on the memory pointer held by the
arena_object. The descriptor is then returned to the unused
arena pool, reducing the process's resident set size (RSS) and returning
the physical memory to the operating system.