Debug Memory Leaks in Python with tracemalloc
Learn how to use Python's built-in tracemalloc module to find and fix memory leaks by tracing allocations to specific lines of code, with practical examples and common patterns.
Debugging Memory Leaks in Python with tracemalloc
You’re running your Python script and everything works fine—until it doesn’t. Memory keeps climbing, your server slows down, and eventually crashes. You’ve got a memory leak. But finding where memory grows is like finding a needle in a haystack.
That’s where tracemalloc comes in. It’s Python’s built-in memory tracker, and it’s surprisingly easy to use.
What is tracemalloc?
Tracemalloc is part of Python’s standard library (no installation needed). It traces every memory allocation in your code, showing you exactly which line created an object that’s still in memory.
Think of it as a GPS for your memory usage. It tells you: - Which objects are using the most memory right now - Where those objects were created (file name, line number) - How memory usage changes over time
Enabling tracemalloc
First, you need to turn it on at the start of your script:
import tracemalloc
tracemalloc.start()
You can put this at the very top of your main script. Once enabled, it silently tracks all memory allocations.
Taking a Snapshot
Now, you take snapshots at different points to compare:
snapshot1 = tracemalloc.take_snapshot()
# ... your code that might leak ...
snapshot2 = tracemalloc.take_snapshot()
Then compare them to see what changed:
stats = snapshot2.compare_to(snapshot1, 'lineno')
for stat in stats[:10]:
print(stat)
This shows you the 10 biggest memory increases, grouped by line number. Each line looks like:
/Path/to/file.py:68: size=1.2 MiB (+1.0 MiB), count=1000 (+800)
This means on line 68 of that file, memory grew by 1 MB, and 800 more objects were created there.
Finding Your Leak in a Long-Running Application
For server applications, you can take periodic snapshots and compare them:
import tracemalloc
import time
tracemalloc.start()
while True:
# Your app logic here
time.sleep(60 * 60) # Wait 1 hour
current = tracemalloc.take_snapshot()
# Save or compare with previous snapshot
You can see which functions keep creating objects without freeing them.
Real-World Example with PythonSkillset
Imagine PythonSkillset hosts a web app where users upload images. After a few days, memory usage doubles. You suspect images aren’t being cleaned up.
import tracemalloc
tracemalloc.start()
# ... later, after 1000 uploads ...
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')
for stat in top_stats[:5]:
print(stat)
You see:
image_processor.py:42: size=500 MiB, count=10000
Bingo. Line 42 of image_processor.py is holding onto image data. You check the code:
def process_image(data):
img = Image.open(data) # line 42
# ... processing ...
return something
Ah! You forgot to close the image after processing. Adding img.close() frees that memory.
Common Memory Leak Patterns
Tracemalloc helps catch these common issues:
- Accumulating data in lists without clearing them
- Open file handles left unclosed
- Circular references (though Python’s garbage collector handles most)
- Caching without limits — your cache keeps growing forever
- Thread locals that retain large objects
Displaying Tracebacks
For a deeper view, use traceback display:
snapshot = tracemalloc.take_snapshot()
top = snapshot.statistics('traceback')
for stat in top[:3]:
print(stat.count, "blocks,", stat.size, "bytes")
for line in stat.traceback:
print(f" {line}")
This shows you the exact call chain that created each memory block.
Performance Impact
Tracemalloc does slow your code a bit (about 10–20% overhead). That’s fine for debugging. Just remember to disable it in production:
tracemalloc.stop() # turn off when done debugging
Or use it only in development environments.
When to Use Tracemalloc vs Other Tools
- Memory profilers like
memory_profilerare good for detailed per-function stats - Objgraph is better for finding circular references
- Tracemalloc excels at tracing which line created each object
It’s the simplest tool for the common case: you know you’re leaking, but you need to find where.
Putting It All Together
Here’s a complete debug script you can drop into any project:
import tracemalloc
def start_tracing():
tracemalloc.start()
def get_top_memory(limit=10):
snapshot = tracemalloc.take_snapshot()
stats = snapshot.statistics('lineno')
for stat in stats[:limit]:
print(stat)
def compare_snapshots(snapshot1, snapshot2, limit=10):
stats = snapshot2.compare_to(snapshot1, 'lineno')
for stat in stats[:limit]:
print(stat)
# Usage
start_tracing()
# Run your code that might leak
get_top_memory()
Final Thoughts
Memory leaks don’t have to be a mystery. With tracemalloc, you get a clear picture of where memory is consumed. It’s built right into Python—no pip install needed. Just enable it, take snapshots, and follow the line numbers.
Next time your Python app starts eating RAM like candy, you know where to look.
If you found this useful, explore more debugging techniques here at PythonSkillset. We keep it practical, so you can solve real problems faster.
Comments
Questions, corrections, and tips stay visible for everyone reading this page.
Join the discussion
No comments yet
Be the first to leave a note — it helps the next reader.