Mental model
Python memory management operates in two layers: Reference Counting for immediate deallocation when reference count hits zero, and a Generational Garbage Collector (Generations 0, 1, 2) to detect cyclical object graphs.
Theory
Every Python object contains a reference count header. When reference count reaches 0, CPython immediately returns memory to PyMalloc arenas. However, self-referencing objects (a.child = b; b.parent = a) form cyclic graphs that reference counting cannot clear. The cyclic GC periodically inspects Generation 0, 1, and 2 containers to break unreachable cyclic references.
Alternatives and trade-offs
- Manual
gc.collect(): Forces immediate cycle cleanup but pauses execution. gc.freeze()in Python 3.8+: Freezes pre-forked parent process memory so Copy-on-Write (CoW) memory pages are not dirtied during worker process forks.
Failure modes and misconceptions
__del__Destructors: In older Python versions, objects with custom__del__methods involved in cycles were uncollectable.- Global Caching Leaks: Storing items in un-bounded global dicts maintains positive reference counts permanently.
Decision scenario
In multi-process web worker deployments (Gunicorn/Uvicorn), call gc.freeze() after loading application code and before worker process forks to optimize Copy-on-Write memory sharing.
Learning outcomes
- Differentiate reference counting from cyclic generational garbage collection.
- Trace memory arena allocations in CPython (
PyMalloc). - Utilize
tracemallocandgc.freeze()for memory optimization in web services.
Trade-offs
Generational garbage collection cleans up circular references automatically, but un-tuned GC sweeps can introduce latency spikes during high-throughput requests.