Open-Source Wikis

/

CPython

/

Systems

/

Garbage collector

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| KEEP

Each 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:

  1. 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.
  2. Mark reachable — start from roots whose external refcount is positive and mark transitively reachable objects via tp_traverse.
  3. Sweep — anything not marked is unreachable through live code. Move it to a finalize list.
  4. 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).
  5. Clear — call tp_clear on 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.c to safely reclaim memory that other threads might still be reading. This is documented in InternalDocs/qsbr.md.
  • Per-object _PyMutex from Python/lock.c protects the per-object data the collector reads.

How a type opts in

A container type:

  1. Sets Py_TPFLAGS_HAVE_GC in tp_flags.
  2. Provides a tp_traverse that calls Py_VISIT(member) on every owned PyObject*.
  3. Provides a tp_clear that drops every reference that could participate in a cycle. (Drops to non-container refs are unnecessary but harmless.)
  4. Allocates with PyObject_GC_New / PyObject_GC_NewVar and frees with PyObject_GC_Del.
  5. Calls PyObject_GC_Track(obj) once the object is fully initialised, and PyObject_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_traverse missing a member → cycles through that member leak.
  • tp_clear clearing 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_traverse must 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 before fork() to keep COW happy.

Entry points for modification

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Garbage collector – CPython wiki | Factory