Memory Management
Memory Management
Section titled โMemory ManagementโWhat it is
Section titled โWhat it isโCPython manages memory primarily through reference counting: every object tracks how many references point to it, and is deallocated the instant that count hits zero. A secondary generational garbage collector exists specifically to catch reference cycles (objects referencing each other in a loop), which reference counting alone can never clean up.
Before this feature
Section titled โBefore this featureโThereโs no โbeforeโ here โ this is how CPython has always worked, so the useful comparison is against languages with a purely tracing garbage collector (Java, Go, C#): those never free an object until a GC pass runs and discovers itโs unreachable, even if nothing references it the instant it becomes unreachable. Pythonโs reference counting frees most objects immediately and deterministically.
After this feature
Section titled โAfter this featureโimport sys
a = [1, 2, 3]print(sys.getrefcount(a)) # 2 -- 'a' itself, plus the temporary arg to getrefcount()
b = a # new reference to the same listprint(sys.getrefcount(a)) # 3
del b # one less referenceprint(sys.getrefcount(a)) # back to 2
del a # refcount hits 0 -- the list is deallocated immediately# Reference cycles: refcounting alone can't free theseimport gc
class Node: def __init__(self): self.other = None
a = Node()b = Node()a.other = bb.other = a # a and b now reference each other
del adel b# Neither object's refcount ever reaches 0 -- they still reference each# other. The generational garbage collector (gc module) finds and frees# cycles like this periodically, even though refcounting alone can't.
gc.collect() # can be triggered manually; also runs automaticallyWhy this is better
Section titled โWhy this is betterโ- Deterministic, immediate cleanup for the common case: most objects (no cycles involved) are freed the instant their last reference disappears โ no unpredictable GC pause, and
with-block resources close/release memory predictably. - Cycle detection covers the gap: the generational GC specifically targets reference cycles, so you get the benefits of refcountingโs immediacy and correctness for cyclic structures, without manually breaking every cycle yourself.
__del__and context managers become reliable: because refcounting is deterministic,__del__runs (for non-cyclic objects) predictably when the last reference goes away, andwithblocks can rely on cleanup happening right at the end of the block.
Key notes / edge cases
Section titled โKey notes / edge casesโ- The generational GC only needs to run on objects that could participate in a cycle โ CPython tracks โcontainerโ objects (that can hold references to other objects, like lists/dicts/instances) separately from simple objects (ints, strings) that structurally canโt form cycles and are handled by refcounting alone.
weakref(see the dedicated page) lets you reference an object without increasing its refcount โ useful for caches or observer patterns where you donโt want your reference to be the thing keeping an object alive.sys.getrefcount(x)is always at least 1 higher than youโd expect, because passingxas an argument togetrefcountitself creates a temporary reference.- CPythonโs refcounting is why the GIL exists in the first place โ incrementing/decrementing a refcount from multiple threads at once without a lock would corrupt the count.
Quick practice
Section titled โQuick practiceโ-
What happens the instant an objectโs reference count reaches zero?
Answer
It's deallocated immediately โ CPython doesn't wait for a garbage-collection pass for non-cyclic objects. -
Why canโt reference counting alone free two objects that reference each other?
Answer
Each one still holds a reference to the other, so neither's count ever reaches zero, even after nothing external references either of them โ that's exactly what a reference cycle is. -
What Python mechanism specifically exists to clean up reference cycles?
Answer
The generational garbage collector (the `gc` module) โ it periodically scans for and frees groups of objects that are unreachable from outside but still reference each other.