Python Memory Management
This page explains how Python manages memory and how to find memory leaks. It shows the tools and patterns that keep your program small and fast.
Names, Identity, and Reference Counts
A name is a reference to an object. The function id() shows the identity of an object. Two names that point to the same object have the same id.
Python counts the references to each object. When the count reaches zero, Python frees the object. The function sys.getrefcount shows the count for an object.
The call sys.getrefcount(x) returns one more than you expect. The argument itself is a temporary reference. This extra reference exists only during the call.
import sys
a = []
b = a
print(id(a) == id(b))
print(sys.getrefcount(a))
del b
print(sys.getrefcount(a))
Cyclic Garbage Collection
Reference counting cannot free a reference cycle. A cycle occurs when objects refer to each other. The cyclic garbage collector finds and frees these cycles.
The function gc.collect() runs a collection now. The function gc.get_count() shows the current count of tracked objects. The function gc.generation shows the generation of an object.
The collector uses three generations. Young objects are checked more often than old objects. This design keeps the cost low.
import gc
class Node:
pass
x = Node()
x.self = x
del x
print(gc.collect())
print(gc.get_count())
Small Object Allocation and Interning
CPython uses pymalloc arenas for small objects. An arena is a block of memory that holds many small objects. This method makes small allocations fast.
Small integers and some strings are interned. Interning means that Python reuses one object for equal values. The identity check a is b can be true for interned values.
Do not rely on interning for your logic. Use == to compare values. Use is only to compare with None or True.
Common Memory Leak Patterns
A global cache that never shrinks keeps objects alive. The fix is to set a maximum size or to use weakref. A closure that keeps a large object alive is another pattern. The fix is to delete the closure or to avoid the capture.
A reference cycle with __del__ can delay collection. The fix is to remove __del__ or to use a weakref callback. Unclosed files and sockets hold resources. The fix is to use a with statement.
Objects kept in module-level lists grow without limit. The fix is to clear the list or to use a bounded structure. Check these patterns when memory grows.
Measure Memory with tracemalloc
The module tracemalloc measures memory allocations. Start it before the code that you want to check. Take a snapshot and compare snapshots to find growth.
The example below allocates a list and shows the top allocation. The output shows the file and the line that allocated the memory. Use this data to find the leak.
import tracemalloc
tracemalloc.start()
data = [str(i) for i in range(10000)]
snapshot = tracemalloc.take_snapshot()
for stat in snapshot.statistics('lineno')[:3]:
print(stat)
tracemalloc.stop()
Reduce Memory with __slots__ and weakref
A normal instance stores its attributes in a dictionary. The dictionary uses memory for each instance. The __slots__ attribute removes the dictionary and stores attributes in a fixed array.
A weakref does not increase the reference count. A cache that uses weakref does not hold objects alive. The object is freed when no strong reference remains.
The example shows a class with __slots__ and a weakref cache. The cache returns None after the strong reference is deleted. This pattern keeps memory small.
import weakref
class Point:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(1, 2)
cache = weakref.WeakValueDictionary()
cache['p'] = p
del p
print(cache.get('p'))
Common questions
Why does sys.getrefcount return one more than expected?
The function call creates a temporary reference to its argument. This reference adds one to the count during the call.
Does del free memory at once?
The del statement removes a name and decreases the reference count. The object is freed only when the count reaches zero.
Why does a leak show up only under load?
A leak grows with each request or allocation. Low load hides the growth because the total memory stays small.