Concept lesson

Python Memory Allocation & GC

PyMalloc arenas, reference counting, cyclic generational garbage collection, and GC tuning.

lesson
Freshness: current15 min read
Mastery
not started · 0%

Learning outcomes

  • Trace reference counting and cyclic GC sweeps
  • Diagnose and fix memory leaks in web services

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.

Object Instantiation
Increment Ref Count
Ref Count == 0 Deallocate
Cyclic Reference detected
Generational GC Sweep
Conceptual teaching model synthesized from:Python 3.12 AsyncIO Event Loop & Asynchronous I/O Specification

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.

import gc

class Node:
    def __init__(self, name: str):
        self.name = name
        self.peer = None

def trigger_cyclic_gc():
    gc.disable()
    n1 = Node("Node1")
    n2 = Node("Node2")
    n1.peer = n2
    n2.peer = n1

    del n1
    del n2
    unreachable_count = gc.collect()
    gc.enable()
    return unreachable_count

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

  1. __del__ Destructors: In older Python versions, objects with custom __del__ methods involved in cycles were uncollectable.
  2. Global Caching Leaks: Storing items in un-bounded global dicts maintains positive reference counts permanently.
Reflect before revealing the guide

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 tracemalloc and gc.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.

Evidence assessment

Theory and decision mastery

not-started · 0%
theory0%
decision0%
activityNot mapped
projectNot mapped
1. What is the primary mechanism Python uses for immediate memory deallocation?
2. Why does CPython need a generational cyclic Garbage Collector in addition to reference counting?
3. How does `gc.freeze()` optimize memory usage in multi-process gunicorn/uvicorn web workers?

Decision scenario

A developer observes a production FastAPI service consuming 4GB RAM after running for 24 hours, even though active traffic is low.

Which diagnostic procedure and fix isolates the memory leak?

Primary sources