Memory Management and Garbage Collection
Memory Management
Section titled “Memory Management”Introduction
Section titled “Introduction”Python handles memory automatically through reference counting and a garbage collector for circular references. Understanding these helps you write efficient code.
flowchart TB subgraph RefCount["Reference Counting (Immediate)"] direction TB RC1["Object created — refcount = 1"] RC2["Another reference — refcount = 2"] RC3["Reference deleted — refcount = 1"] RC4["Last ref deleted — refcount = 0 🗑️"] RC5["Memory freed immediately"] RC1 --> RC2 --> RC3 --> RC4 --> RC5 end
subgraph GC["Garbage Collector (Cycle Detector)"] direction TB GC1["Object A → Object B"] GC2["Object B → Object A"] GC3["Both unreachable from outside"] GC4["GC detects cycle 🔄"] GC5["Memory freed"] GC1 --- GC2 GC2 --> GC3 --> GC4 --> GC5 end
style RefCount fill:#059669,color:#fff style GC fill:#7c3aed,color:#fff style RC1 fill:#10b981,color:#fff style RC2 fill:#10b981,color:#fff style RC3 fill:#f59e0b,color:#fff style RC4 fill:#ef4444,color:#fff style RC5 fill:#059669,color:#fff style GC1 fill:#8b5cf6,color:#fff style GC2 fill:#8b5cf6,color:#fff style GC3 fill:#f59e0b,color:#fff style GC4 fill:#ef4444,color:#fff style GC5 fill:#059669,color:#fffReference Counting
Section titled “Reference Counting”import sys
x = [1, 2, 3]print(sys.getrefcount(x)) # 2 (x + argument)
y = xprint(sys.getrefcount(x)) # 3 (x, y, argument)
del yprint(sys.getrefcount(x)) # 2 (back to x + argument)
# When reference count reaches 0, memory is freedThe Garbage Collector
Section titled “The Garbage Collector”import gc
# Enable/disable GCgc.enable()# gc.disable()
# Circular reference — GC handles thisclass Node: def __init__(self, name): self.name = name self.next = None
a = Node("A")b = Node("B")a.next = bb.next = a # Circular reference!
del a, b # Reference count won't free these, but GC will
# Manual GC collectioncollected = gc.collect()print(f"Collected {collected} objects")
# Get GC statsprint(gc.get_stats())
# Debug memorygc.set_debug(gc.DEBUG_LEAK)Memory Optimization
Section titled “Memory Optimization”# Use __slots__ to save memory (no __dict__)class Point: __slots__ = ('x', 'y')
def __init__(self, x, y): self.x = x self.y = y
# Compare memory usageimport sysclass PointNormal: def __init__(self, x, y): self.x = x self.y = y
p1 = Point(3, 4)p2 = PointNormal(3, 4)print(sys.getsizeof(p1)) # ~56 bytes (with __slots__)print(sys.getsizeof(p2)) # ~72 bytes (with __dict__)Memory Profiling
Section titled “Memory Profiling”# Using sys.getsizeofimport sysprint(sys.getsizeof(42)) # 28 bytesprint(sys.getsizeof("hello")) # 54 bytesprint(sys.getsizeof([1,2,3,4,5])) # 104 bytes + elements
# Using tracemallocimport tracemalloc
tracemalloc.start()# Your code heresnapshot = tracemalloc.take_snapshot()top_stats = snapshot.statistics('lineno')for stat in top_stats[:5]: print(stat)Best Practices
Section titled “Best Practices”- Use
__slots__for classes with many instances (thousands+) - Use generators instead of lists for large datasets
- Be aware of circular references — they delay memory deallocation
- Use
weakrefwhen you need references without increasing reference count - Profile memory with
tracemallocbefore optimizing
Practice Exercises
Section titled “Practice Exercises”Exercise 1: Create a large number of objects with and without __slots__ and compare memory usage.
Exercise 2: Create a cache using weakref.WeakValueDictionary that doesn’t prevent garbage collection.