Memory Management and Garbage Collection
How CPython allocates, tracks and reclaims objects at runtime
- explain how reference counting decides when an object is freed, and why
sys.getrefcountalways reads one high; - describe how reference cycles defeat pure refcounting and how the generational collector reclaims them;
- use
weakrefto observe or cache an object without keeping it alive; - avoid the classic
__del__pitfalls (resurrection, undefined cycle order, shutdown); - diagnose unexpected memory growth with
tracemalloc,gcandsys.getsizeof.
This topic assumes you already know Python's object model — identity, mutability and copy semantics from the previous topic. Here we go one level deeper, into the runtime machinery that keeps those objects alive.
1. Reference counting: the primary mechanism
Every object CPython creates carries a small integer field, ob_refcnt, that counts how many references currently point at it. Binding a name, appending to a list, passing an argument — each of these adds one to the count; deleting a name, rebinding it, or letting a scope exit subtracts one. The rule is exact and immediate: the moment the count reaches zero, the object is deallocated, its __del__ (if any) runs, and its own references are released — which may cascade and free still more objects.
import sys
a = [10, 20, 30] # the list object now has one reference: 'a'
b = a # a second reference to the SAME object
print(sys.getrefcount(a)) # 3 (a, b, and getrefcount's own argument)
del b # one reference gone
print(sys.getrefcount(a)) # 2 (a, and the argument)
del a # last real reference gone -> refcount hits 0 -> freed now
This is why CPython frees most memory deterministically and promptly, with no "stop the world" pause: reclamation is woven into ordinary reference bookkeeping. It is also why a file closed by a context manager is released right away when the with block ends and its last reference disappears.