Memory and GC
Python memory behavior is different from tracing-GC languages such as Go or Java because CPython combines reference counting with cyclic garbage collection. Many objects disappear immediately, but cycles need a separate collection step.
Quick takeaway: CPython memory is best understood as "immediate cleanup from reference counting, plus separate cycle detection." That makes resource lifetime more predictable in some cases, but large cyclic graphs and pause behavior still matter.
Memory-Reclamation Flow
Small Cycle Example
import gc
class Node:
def __init__(self, name: str) -> None:
self.name = name
self.other: Node | None = None
left = Node("left")
right = Node("right")
left.other = right
right.other = left
del left
del right
print("collected objects:", gc.collect())The two nodes keep each other alive through a reference cycle. Reference counting alone cannot reclaim them once the external references disappear.
pymalloc Intuition
- CPython uses a specialized allocator for many small objects.
- "This object was freed" is not the same as "memory was returned to the OS immediately."
- That is why process RSS does not always fall as quickly as you might expect.
The 3.14 Direction
- cyclic-GC work is moving toward smaller incremental steps
- the goal is lower pause impact for latency-sensitive workloads
- memory-management evolution is tied to the broader runtime changes around concurrency
Practical Connections
- large object graphs
- file and socket lifecycle
- memory-leak debugging
Checklist
Do not rely on GC for resource cleanup
Files, sockets, and sessions should usually be closed explicitly or through context managers.
Watch for cycles
Graphs, listeners, and back-references are common sources of cyclic retention.
Do not read RSS alone
Allocator behavior means stable RSS is not automatically a memory leak.
Use the right tools
`gc`, tracemalloc, and object-graph inspection together are much more informative than one metric alone.