python/cpython
Garbage collector
CPython is primarily reference-counted. The cycle collector documented on this page exists only to break reference cycles that pure refcounting cannot reclaim. The authoritative reference is InternalDocs/garbage_collector.md, which is unusually detailed (~38k of prose).
Two cycle collectors, one project
Since 3.13, CPython has two implementations of the cycle GC, selected at build time:
| Build | File | Notes |
|---|---|---|
| Default (with GIL) | Python/gc.c |
The classic, generational, mark-and-sweep cycle collector. |
--disable-gil |
Python/gc_free_threading.c |
A separate implementation tuned for free-threaded execution. |
| Glue | Python/gc_gil.c |
A tiny stub that picks one of the above at link time. |
| Public C bindings | Modules/gcmodule.c |
The gc Python module surface. |
| GC headers | Include/internal/pycore_gc.h |
PyGC_Head, generation structures, traversal flags. |
| Weakref interaction | Modules/gc_weakref.txt |
Notes on how weakrefs are cleared during collection. |
Both collectors target container objects — types that set Py_TPFLAGS_HAVE_GC and provide a tp_traverse slot. Atomic types (int, float, str, …) do not participate.
Generational mark-and-sweep (GIL build)
graph LR
NEW[Newly tracked container] --> G0[Generation 0]
G0 -->|survives a collection| G1[Generation 1]
G1 -->|survives a collection| G2[Generation 2]
G2 -->|cycles| FREE[free]
G0 -->|reachable| KEEP[keep]
G1 -->|reachable| KEEP
G2 -->|reachable| KEEPEach container is in exactly one of three generations. Allocation thresholds (default 700 / 10 / 10) trigger a collection of the youngest generation that overflowed. Older generations are collected less frequently.
The algorithm for one collection:
- Snapshot refcounts — each tracked container's "external" refcount is computed by subtracting the references from other tracked containers in this generation. If the result is zero, only cycles can be holding the object alive.
- Mark reachable — start from roots whose external refcount is positive and mark transitively reachable objects via
tp_traverse. - Sweep — anything not marked is unreachable through live code. Move it to a finalize list.
- Finalize — call
tp_finalize(PEP 442) on each unreachable object. If finalizers resurrect the object, it is moved back to the live set (possibly into an older generation). - Clear — call
tp_clearon the survivors of step 4 to break the cycle, then drop refcounts. Refcounting handles the actual frees.
Implementation: gc_collect_main in Python/gc.c. The merge of the generational GC into the free-threaded build was the topic of significant 3.15 work (see commits referencing GH-148746).
Free-threaded GC
Python/gc_free_threading.c is a parallel implementation that does not require the GIL. Distinguishing characteristics:
- All threads stop the world during a collection, but each thread participates in marking via per-thread work queues.
- Refcounts are biased — every object is owned by a thread; cross-thread INCREF/DECREF use atomic ops, the owner uses non-atomic ops.
- The collector cooperates with QSBR (Quiescent-State Based Reclamation) in
Python/qsbr.cto safely reclaim memory that other threads might still be reading. This is documented inInternalDocs/qsbr.md. - Per-object
_PyMutexfromPython/lock.cprotects the per-object data the collector reads.
How a type opts in
A container type:
- Sets
Py_TPFLAGS_HAVE_GCintp_flags. - Provides a
tp_traversethat callsPy_VISIT(member)on every ownedPyObject*. - Provides a
tp_clearthat drops every reference that could participate in a cycle. (Drops to non-container refs are unnecessary but harmless.) - Allocates with
PyObject_GC_New/PyObject_GC_NewVarand frees withPyObject_GC_Del. - Calls
PyObject_GC_Track(obj)once the object is fully initialised, andPyObject_GC_UnTrack(obj)before tearing it down.
A correct example is Objects/dictobject.c: dict_traverse, dict_tp_clear, and the Py_TPFLAGS_HAVE_GC flag.
Common bugs the collector reveals
tp_traversemissing a member → cycles through that member leak.tp_clearclearing too eagerly → unrelated finalizers see partly-cleared objects.- Forgetting
PyObject_GC_Track→ the object is never collected; this is also a leak from the GC's perspective. - Calling untrusted Python code from
tp_traverse→ re-entrancy hazard.tp_traversemust be safe under "everything is half-collected".
The gc.set_debug flags (gc.DEBUG_LEAK, gc.DEBUG_SAVEALL) and gc.get_objects are the workhorses for debugging.
Weakrefs and finalizers
When the collector clears a cycle, weakrefs to the unreachable objects are cleared (their callbacks are called) before finalizers run. The detailed ordering is described in Modules/gc_weakref.txt and the implementation lives in Modules/gc.* and Objects/weakrefobject.c.
A __del__ method becomes the type's tp_finalize slot (PEP 442). Unlike pre-3.4 Python, an object with __del__ participating in a cycle can be collected — it is finalized at most once and then released or resurrected.
Configuration
The Python gc module exposes:
gc.collect([generation])— force a collection.gc.disable()/gc.enable()— toggle the collector globally.gc.get_threshold()/gc.set_threshold(t0, t1, t2)— adjust the per-generation thresholds.gc.get_count(),gc.get_stats()— instrumentation.gc.freeze()/gc.unfreeze()— move all currently-live containers into a permanent "frozen" set that the collector skips. Useful beforefork()to keep COW happy.
Entry points for modification
- The cycle algorithm —
Python/gc.c(GIL) orPython/gc_free_threading.c(nogil). - A new tracked type — set
Py_TPFLAGS_HAVE_GC, providetp_traverse/tp_clear, allocate viaPyObject_GC_*. - A new
gc.*Python API —Modules/gcmodule.c. - Weakref interaction —
Objects/weakrefobject.candModules/gc_weakref.txt.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.