Ei vielä käännetty
Tätä sivua ei ole vielä käännetty suomeksi, joten se näytetään englanniksi. Auta kääntämään
gc Module Complexity¶
The gc module controls the cyclic garbage collector. Reference counting frees most objects the
moment their last reference goes; the collector exists for reference cycles, which reference
counting cannot free. It tracks container objects - lists, dicts, class instances, functions - in
generations, and a collection walks the objects of the generations it examines and every reference
they hold. Apart from what it appends to gc.garbage, a collection allocates nothing per object:
its unit of work is objects and references visited, not memory.
n is the objects the collector tracks outside the permanent generation that freeze() fills, and
e is the references those objects hold, whether or not the referenced object is tracked: a list
of a million ints is one tracked object and a million references. References are counted as the
slots a container walks, so a dict or set costs its table size, which deletions do not shrink.
g is the objects in the generations one call names and h the references they hold, f is
frozen objects, k is the objects passed to get_referrers() or get_referents(), taken as at
least one, a is the references the arguments of get_referents() hold, r is the objects
returned, and c is the callbacks in gc.callbacks. Finalizers, weak reference callbacks and
gc.callbacks functions that a collection runs are the caller's code and are not priced. The
bounds are for the standard build; the free-threaded build has a different collector.
Complexity Reference¶
Collection¶
| Operation | Time | Space | Notes |
|---|---|---|---|
gc.collect(generation=2) |
O(n + e) | O(1) | A full collection; frozen objects are skipped. Space excludes what DEBUG_SAVEALL keeps |
gc.collect(0), gc.collect(1) |
O(g + h) | O(1) | Generation 0, or generations 0 and 1; older objects are not visited |
| Automatic collection | Amortized O(1 + e/n) per allocation | O(1) | Plus the new object's references; a full collection runs once the survivors grow by a quarter, and costs n + e |
gc.enable(), gc.disable(), gc.isenabled() |
O(1) | O(1) | Switch automatic collection only; collect() still works while disabled |
gc.get_threshold(), gc.set_threshold(threshold0[, threshold1[, threshold2]]) |
O(1) | O(1) | A threshold0 of 0 also disables automatic collection |
gc.get_count() |
O(1) | O(1) | The three counters the thresholds are compared against |
gc.get_stats() |
O(1) | O(1) | One dict per generation, three in all; each gains candidates and duration keys from 3.15 |
Inspecting objects¶
| Operation | Time | Space | Notes |
|---|---|---|---|
gc.get_objects(generation=None) |
O(n) | O(n) | Frozen objects are not included |
gc.get_objects(generation) |
O(g) | O(g) | Only the named generation is walked |
gc.get_referrers(*objs) |
O(n + k·e) | O(r) | Walks every tracked object and checks each reference against all k arguments; frozen objects are not searched |
gc.get_referents(*objs) |
O(k + a) | O(a) | Only the arguments' own direct references; the rest of the heap is not visited |
gc.is_tracked(obj) |
O(1) | O(1) | Ints, strings and other atomic objects are never tracked |
gc.is_finalized(obj) |
O(1) | O(1) | True once the interpreter has finalized the object; it never does so twice |
Freezing¶
| Operation | Time | Space | Notes |
|---|---|---|---|
gc.freeze() |
O(1) | O(1) | Moves every tracked object into the permanent generation, which no collection visits |
gc.unfreeze() |
O(1) | O(1) | Moves the permanent generation back into the oldest one |
gc.get_freeze_count() |
O(f) | O(1) | Counts the permanent generation by walking it |
Debugging, garbage and callbacks¶
| Operation | Time | Space | Notes |
|---|---|---|---|
gc.set_debug(flags), gc.get_debug() |
O(1) | O(1) | |
gc.DEBUG_STATS, gc.DEBUG_COLLECTABLE, gc.DEBUG_UNCOLLECTABLE, gc.DEBUG_SAVEALL, gc.DEBUG_LEAK |
O(1) | O(1) | Integer flags; DEBUG_SAVEALL, part of DEBUG_LEAK, keeps every unreachable object in gc.garbage instead of freeing it |
gc.garbage |
O(1) | O(1) | A plain list; the collector appends to it and never empties it |
gc.callbacks |
O(1) | O(1) | A plain list; every collection calls each callback twice, adding O(c) calls |
Collecting Cycles¶
Full and Young Collections¶
A full collection costs every tracked object and every reference they hold, whether or not anything is garbage. Collecting generation 0 visits only the objects that became tracked since the last collection, so it stays cheap however large the rest of the heap is.
import gc
import weakref
class Node:
pass
node = Node()
node.self = node # a cycle reference counting cannot free
alive = weakref.ref(node)
del node
assert alive() is not None
gc.collect() # O(n + e) - a full collection
assert alive() is None
survivors = [[] for _ in range(1_000)]
gc.collect(0) # O(g + h) - generation 0 only
assert gc.get_objects(0) == [] # O(g) - the survivors moved to an older generation
Automatic Collection¶
Once allocations of container objects minus their deallocations exceed threshold0, generation 0
is collected; once that has happened more than threshold1 times, generation 1 is collected with
it. A full collection also waits until the objects that survived since the last one number a
quarter of those it kept. Each full walk is paid for by the quarter that triggered it, so building
a large structure costs amortized O(1 + e/n) per object rather than a full walk every few thousand
allocations: O(1) while objects hold a few references each, more when a few old containers hold
most of the references. gc.disable() or a threshold0 of 0 stops the
automatic collections; collect() works either way.
import gc
previous = gc.get_threshold() # O(1)
gc.set_threshold(100, 10, 10) # O(1)
assert gc.get_threshold()[:2] == (100, 10)
runs = []
gc.callbacks.append(lambda phase, info: runs.append(info['generation']))
kept = [[] for _ in range(1_000)] # amortized O(1 + e/n) per list
gc.callbacks.clear()
assert runs # collections ran along the way, without a call to collect()
assert len(gc.get_count()) == 3 # O(1)
stats = gc.get_stats() # O(1)
assert len(stats) == 3
assert all({'collections', 'collected', 'uncollectable'} <= set(s) for s in stats)
gc.disable() # O(1)
assert not gc.isenabled()
gc.enable()
gc.set_threshold(*previous)
Finding References¶
get_referents() reads only the arguments' own references. get_referrers() has no index to
consult: it walks every tracked object in the heap and checks each of its references against every
argument, so it is the costly direction and grows with the number of targets as well as the heap.
import gc
target = {'value': 1}
holder = [target]
assert gc.get_referents(holder) == [target] # O(k + a) - direct references only
assert any(r is holder for r in gc.get_referrers(target)) # O(n + k·e)
assert gc.is_tracked(holder) # O(1)
assert not gc.is_tracked(42) and not gc.is_tracked('text')
Freezing Before a Fork¶
freeze() moves every tracked object into a permanent generation that collections skip, which
keeps collections in a forked child from writing to the frozen objects, so their pages stay shared,
and makes later collections cheaper.
Freezing and unfreezing move whole generations at once; get_freeze_count() is the one that
walks, so it costs the frozen object count.
import gc
held = [[] for _ in range(1_000)]
gc.freeze() # O(1)
assert gc.get_freeze_count() >= 1_000 # O(f)
assert not any(o is held for o in gc.get_objects()) # frozen objects are not listed
assert gc.get_referrers(held[0]) == [] # nor searched
gc.collect() # O(n + e) - n excludes the frozen objects
gc.unfreeze() # O(1)
assert gc.get_freeze_count() == 0
assert any(r is held for r in gc.get_referrers(held[0]))
Debugging Leaks¶
DEBUG_SAVEALL makes every collection append what it finds unreachable to gc.garbage instead of
freeing it, so memory grows with each collection until you empty the list. Callbacks in
gc.callbacks are called with 'start' and 'stop' around each collection.
import gc
events = []
def record(phase, info):
events.append((phase, info['generation']))
gc.callbacks.append(record) # O(1)
gc.set_debug(gc.DEBUG_SAVEALL) # O(1)
assert gc.get_debug() == gc.DEBUG_SAVEALL
assert gc.DEBUG_LEAK & gc.DEBUG_SAVEALL
cycle = []
cycle.append(cycle)
del cycle
gc.collect()
assert any(len(o) == 1 and o[0] is o for o in gc.garbage if isinstance(o, list))
assert events[-2:] == [('start', 2), ('stop', 2)]
gc.set_debug(0)
gc.garbage.clear()
gc.callbacks.remove(record)
Finalized Objects¶
is_finalized() reports whether the interpreter has already run an object's __del__ as its
finalizer, which matters for an object that __del__ resurrected: it is not finalized a second
time. Calling __del__ yourself does not count.
import gc
saved = []
class Phoenix:
def __del__(self):
saved.append(self) # resurrect it
bird = Phoenix()
assert not gc.is_finalized(bird) # O(1)
bird.self = bird
del bird
gc.collect()
assert gc.is_finalized(saved[0])
Common Patterns¶
Building a Large Structure¶
Automatic collection keeps a bulk build of small objects linear, but each young collection still
walks the new objects, and each full one walks the whole heap. Pausing the collector for the build
removes those walks; switch it back on in a finally so an exception cannot leave it off.
import gc
was_enabled = gc.isenabled()
gc.disable() # O(1)
try:
table = {i: [i] for i in range(10_000)} # no collections during the build
finally:
if was_enabled:
gc.enable()
assert len(table) == 10_000
Performance Best Practices¶
✅ Do:
- Walk outward from objects you hold with
get_referents()where that answers the question: it reads those objects, whileget_referrers()walks the heap - Pass every target to one
get_referrers()call rather than one call each: a call walks the whole heap once, however many targets it checks - Call
gc.freeze()beforefork()in a server that preloads data, so neither process walks it - Collect generation 0 when you only need recently created cycles gone
❌ Avoid:
gc.collect()in a loop: each call walks every tracked object and referenceget_freeze_count()in a hot path: it walks the frozen objects- Leaving
DEBUG_SAVEALLon:gc.garbagethen keeps every unreachable object ever found
Version Notes¶
- Python 3.14.0 to 3.14.4: the collector is incremental, with a young and an old generation.
collect(1)collects the young generation and an increment of the old one,get_objects(1)is always empty, andfreeze()walks the young generation and one of the old generation's two spaces, O(n). Python 3.14.5 restores the three generations - Python 3.14+:
is_tracked()is true for every dict; earlier releases untrack a dict whose keys and values are all atomic - Python 3.15+:
get_stats()entries, and theinfodict passed to agc.callbacksfunction at the"stop"phase, gaincandidatesandduration
Related Modules¶
- sys -
getrefcount()andgetsizeof(), which look at one object, not the heap - weakref - references that do not keep an object alive, so form no cycle
- tracemalloc - where memory was allocated, not who holds it
- timeit - turns the collector off while it measures