Object Model, Reference Counting & Generational GC
Memory management in CPython combines primary Reference Counting with a secondary Generational Cyclic Garbage Collector (GC). Understanding the PyObject header struct, reference count mutations (Py_INCREF / Py_DECREF), cyclic reference deadlocks, and Generational GC sweeps (Gen 0, Gen 1, Gen 2) is fundamental to preventing memory leaks in large-scale Python systems.
This chapter details the PyObject C header struct, Reference Counting mechanics, cyclic reference memory leaks, and CPythonβs Generational GC algorithm.
1. The PyObject C Struct Header
Every Python object on the heap starts with a standard C struct header defined in object.h:
CPython PyObject C Header Struct:
[ PyObject @ Heap Address 0x7F9A ]
βββ ob_refcnt (uint64_t / 8 Bytes): Live reference count
βββ ob_type (PyTypeObject* / 8 Bytes): Pointer to object's Type struct
[ Object Payload Data (e.g. integer value, string bytes, array pointers) ]ob_refcnt: Stores the exact number of active references pointing to this object.ob_type: Pointer to thePyTypeObjectdefining the objectβs class behavior and C-slots.
2. Primary Memory Management: Reference Counting
CPython tracks memory allocations deterministically using Reference Counting:
Py_INCREF(op): Incrementsob_refcntby 1 whenever an object is assigned to a variable, passed to a function, or inserted into a list.Py_DECREF(op): Decrementsob_refcntby 1 whenever a reference goes out of scope (del, function return, list item removal).
Deterministic Deallocation: The exact instant
ob_refcntdrops to 0, CPython immediately deallocates the objectβs memory and frees its memory block back to the memory allocator!
import sys
x = [1, 2, 3]
print(sys.getrefcount(x)) # Prints: 2 (x + sys.getrefcount temporary arg reference!)
y = x
print(sys.getrefcount(x)) # Prints: 3 (x + y + temporary arg reference!)
del y
print(sys.getrefcount(x)) # Prints: 23. The Cyclic Reference Flaw & Generational GC
Reference counting has one critical failure mode: Cyclic References.
Cyclic Reference Memory Deadlock:
[ Object A ] (ob_refcnt = 1) βββ References βββ> [ Object B ] (ob_refcnt = 1)
^ |
ββββββββββββββββββ References βββββββββββββββββββββ
(If local variable pointers to A and B are deleted, both ob_refcnt remain 1!
Reference Counting CANNOT deallocate them! Memory LEAK occurs!)Generational Garbage Collector (GC):
To clean up un-reachable cyclic reference islands, CPython runs an asynchronous Generational Cyclic Garbage Collector (gc module):
- Generation 0 (Gen 0): Youngest objects. Scanned frequently (every 700 allocations).
- Generation 1 (Gen 1): Intermediate objects that survived a Gen 0 GC collection sweep.
- Generation 2 (Gen 2): Long-lived objects (survived Gen 1 collection). Scanned infrequently.
Tri-Color Marking Algorithm:
The GC detects cycles by subtracting internal reference counts across doubly-linked lists of container objects (PyGC_Head). If an objectβs reference count drops to zero after subtracting internal references within the collection group, it is identified as unreachable cyclic garbage and freed.
4. Production Trade-offs & gc.disable()
gc.disable()for High-Throughput Batch Jobs: In multi-process workers (like Gunicorn or Celery), running full Gen 2 GC collections during execution can cause Stop-The-World pause latency spikes. Disabling GC (gc.disable()) or tuning thresholds (gc.set_threshold()) during request execution improves latency if code avoids cyclic references.