Skip to main content

Python/gc.c (part 3)

Source:

cpython 3.14 @ ab2d84fe1023/Python/gc.c

This annotation covers the public GC API and generational tuning. See python_gc_detail for the mark-and-sweep algorithm, and python_gc2_detail for finalization and tp_finalize.

Map

LinesSymbolRole
1-100gc.collectTrigger a collection of one or all generations
101-220Generational thresholdsgc.get_threshold, gc.set_threshold, automatic triggering
221-360gc.get_count / gc.get_statsQuery current object counts and collection statistics
361-500gc.is_tracked / gc.is_finalizedPer-object GC state queries
501-700gc.freeze / gc.unfreezeMove generation 2 objects to a permanent "frozen" set

Reading

gc.collect

// CPython: Python/gc.c:1820 gc_collect_impl
static Py_ssize_t
gc_collect_impl(PyObject *module, int generation)
{
/* Collect the given generation (0=young, 1=middle, 2=old).
Collecting generation N also collects all younger generations. */
GCState *gcstate = &tstate->interp->gc;
Py_ssize_t n = 0;
for (int i = 0; i <= generation; i++) {
n += gc_collect_main(tstate, i, &gcstate->stats[i]);
}
return n;
}

gc.collect() triggers a full collection (all 3 generations). gc.collect(0) collects only generation 0. Returns the number of unreachable objects found.

Generational thresholds

// CPython: Python/gc.c:1700 gc_set_threshold_impl
static PyObject *
gc_set_threshold_impl(PyObject *module, int threshold0, int threshold1,
int threshold2)
{
/* Collect generation N when objects in that generation exceed threshold_N.
Defaults: threshold0=700, threshold1=10, threshold2=10 */
GCState *gcstate = &tstate->interp->gc;
gcstate->generations[0].threshold = threshold0;
gcstate->generations[1].threshold = threshold1;
gcstate->generations[2].threshold = threshold2;
Py_RETURN_NONE;
}

Each allocation increments the generation-0 count. When it reaches threshold0 (default 700), a generation-0 collection runs. After threshold1 (default 10) generation-0 collections, a generation-1 collection also runs. After threshold2 (default 10) generation-1 collections, generation 2 is collected too.

gc.get_stats

// CPython: Python/gc.c:1750 gc_get_stats_impl
static PyObject *
gc_get_stats_impl(PyObject *module)
{
/* Return a list of dicts, one per generation:
{'collections': N, 'collected': M, 'uncollectable': K} */
PyObject *result = PyList_New(NUM_GENERATIONS);
for (int i = 0; i < NUM_GENERATIONS; i++) {
GCGenStats *stats = &gcstate->stats[i];
PyObject *d = PyDict_New();
PyDict_SetItemString(d, "collections", PyLong_FromSsize_t(stats->collections));
PyDict_SetItemString(d, "collected", PyLong_FromSsize_t(stats->collected));
PyDict_SetItemString(d, "uncollectable", PyLong_FromSsize_t(stats->uncollectable));
PyList_SET_ITEM(result, i, d);
}
return result;
}

uncollectable counts objects put in gc.garbage (objects with __del__ methods that are part of reference cycles — these cannot be automatically finalized in Python 2; in Python 3 they are handled but still counted if gc.callbacks report them uncollectable).

gc.freeze

// CPython: Python/gc.c:1900 gc_freeze_impl
static PyObject *
gc_freeze_impl(PyObject *module)
{
/* Move all objects currently in generation 2 to a 'permanent' set
that is never collected. Used after importing all standard library
modules to avoid re-scanning them on every full GC. */
GCState *gcstate = &tstate->interp->gc;
PyGC_Head *gen2 = &gcstate->generations[2].head;
gc_list_merge(gen2, &gcstate->permanent_generation.head);
gcstate->generations[2].count = 0;
Py_RETURN_NONE;
}

gc.freeze() is called by Python's startup code after importing the standard library. Frozen objects are never scanned for cycles, reducing per-collection work significantly for long-running applications.

gopy notes

gc.collect is vm.GCCollect in vm/gc.go. Generational thresholds are vm.GCState.Thresholds. gc.freeze/unfreeze move objects between the gen2 list and permanentGen list. gc.get_stats returns a []objects.Dict with the collection statistics.