GC: Reference Counting + Generational Cyclic Garbage Collector
- Last updated
- Prerequisites:
- CPython Object Model (`PyObject`)
- Graph cycles and pointers
- python
- cpython
- garbage-collection
- reference-counting
- cyclic-gc
- memory-leaks
Read at your depth
The practical view
CPython's primary memory management is **Reference Counting** (`ob_refcnt`). When an object's reference count reaches `0`, it is deallocated immediately and synchronously. To solve **Cyclic References** (where Object A references B and B references A, keeping `refcnt >= 1` forever), CPython has a secondary **Generational Cyclic Garbage Collector** (`gc` module) with 3 generations (Gen 0, Gen 1, Gen 2).
The same idea in other frameworks
Reference Counting (immediate) + Generational Cyclic GC (3 generations).Tri-color Mark-Sweep concurrent collector (no reference counting).Generational G1 / ZGC tracing collectors (no reference counting).Compile-time RAII ownership (`Drop`) + explicit `Arc`/`Rc` reference counting.Legacy vs modern
Immediate Reference Count Deallocation vs Cyclic Reference Leak
Linear references die immediately on scope exit; cyclic references stay in memory until the cyclic GC runs.
nodeA.other = nodeB; nodeB.other = nodeA; del nodeA, nodeB; // ❌ refcnt stays 1, requires GC cycleimport weakref; nodeB.other = weakref.ref(nodeA); // ✅ NEW: Instant refcnt=0 deallocationInterview gotchas
Context
Interviewers test your historical knowledge of CPython's finalization mechanics.
The mechanical answer
Prior to Python 3.4, if two cyclic objects both defined a `__del__` destructor, Python had no safe way to determine which destructor should execute first without passing a half-destroyed object to the other. Consequently, CPython threw the cycle into `gc.garbage` and **leaked the memory forever**. In Python 3.4+ (PEP 442), Python modernized finalization: it breaks the cycle before calling `__del__`, allowing cyclic objects with destructors to be safely collected.
Trap
Even in modern Python, relying on `__del__` for critical cleanup is an anti-pattern; always use Context Managers (`with` blocks).
Context
Checks performance tuning and high-throughput production optimization skills.
The mechanical answer
Yes. Calling `gc.disable()` **disables the generational cyclic collector, but Reference Counting continues to function 100% normally**. 99% of non-cyclic objects are still deallocated immediately when their reference count reaches 0. High-scale services (like Instagram/Instagram Web) disable the cyclic GC to prevent GC pause latency during peak traffic, manually invoking `gc.collect()` during idle periods.
Trap
`gc.disable()` will cause cyclic graphs to leak permanently unless broken manually via `weakref` or periodic manual `gc.collect()`.