Reference counting is the primary mechanism
CPython's main memory manager is reference counting. Every object header carries ob_refcnt. Creating a reference increments it; dropping one decrements it. The instant the count hits zero the object is freed immediately: its finalizer (__del__, if any) runs, and the references it held are decremented in turn, which can cascade.
import sys
a = object()
sys.getrefcount(a) # e.g. 2
b = a
sys.getrefcount(a) # 3
del b
sys.getrefcount(a) # 2
The upside is determinism: an object dies as soon as its last reference disappears, so under CPython files and sockets tend to close promptly. The downsides are per-operation overhead (every bind touches a counter) and one structural gap counting cannot close by itself: reference cycles.

