Introduction
Memory leaks in Python can cripple production services, causing latency spikes, OOM crashes, and lost revenue. This guide walks you through diagnosing, fixing, and preventing leaks in live environments.
How Python Manages Memory
- Reference counting: primary mechanism; objects are freed when count drops to zero.
- Garbage collector (gc): detects cyclic references that reference counting misses.
- Object pools: built‑in types may cache objects (e.g., small integers, strings). Understanding these layers helps you pinpoint why an object stays alive.
Common Leak Patterns
- Global containers that keep references forever.
-
C extensions that forget to
Py_DECREF. -
Reference cycles involving objects with
__del__. - Unbounded caches (e.g., dicts used as ad‑hoc memoizers).
Detecting Leaks in Production
-
Enable
tracemallocto snapshot allocations. -
Monitor RSS with
psutilto spot growth. - Take periodic heap dumps and compare snapshots.
import tracemalloc, psutil, time, os
def monitor():
tracemalloc.start()
proc = psutil.Process(os.getpid())
while True:
snapshot = tracemalloc.take_snapshot()
top = snapshot.statistics('lineno')[:5]
print(f"RSS: {proc.memory_info().rss / 1024**2:.2f} MB")
for stat in top:
print(stat)
time.sleep(30)
# monitor()
Step‑by‑Step Fix Guide
- Identify the hot path – use the monitor above to locate the function whose memory use climbs.
-
Break reference cycles – replace
__del__with explicitclosemethods or useweakref. -
Replace global caches with
functools.lru_cacheorweakref.WeakValueDictionary. -
Scope objects tightly – use context managers (
with) to ensure timely cleanup. - Deploy a watchdog – restart workers gracefully when RSS exceeds a threshold.
Example: Refactoring a Leaky Cache
# Leaky version
cache = {}
def get_user(uid):
if uid not in cache:
cache[uid] = load_from_db(uid)
return cache[uid]
The dictionary grows without bound. Fix with an LRU cache:
from functools import lru_cache
@lru_cache(maxsize=1024)
def get_user(uid):
return load_from_db(uid)
Or use a weak‑value dict if objects can be reclaimed elsewhere:
import weakref
cache = weakref.WeakValueDictionary()
Automate the Fix
We've prepared a script that scans your codebase for common leak patterns. Download the pre‑configured script here. You can also Get the complete patch tool for CI integration, or Access the full repository fix.
Best Practices
- Use
withstatements for files, sockets, DB connections. - Prefer generators over building large lists.
- Limit object lifetimes; avoid globals for temporary data.
- Run the above monitor in staging before each release.
Conclusion
Detecting and fixing Python memory leaks in production is a systematic process: monitor, snapshot, isolate, and refactor. With the right tooling and disciplined code patterns, you can keep your services fast, stable, and cost‑effective.
Top comments (0)