你是不是也遇到过这种诡异现象?
服务器跑得好好的,突然就卡成PPT?
top一看,Python进程占了80%内存?
别急,这大概率不是系统问题——而是你的代码在偷偷“吃内存”。
Python有垃圾回收机制没错,但引用没断干净、缓存没清、循环引用没处理,照样会内存泄漏。
我曾在一个数据清洗脚本里,硬是让16GB内存爆到OOM(Out of Memory),差点被运维拉去喝茶 ☕。
内存泄漏的三大“惯犯”,你中了几个?
1. 全局变量或类属性无节制增长
比如把每次请求结果 append 到一个全局 list:
results = []
defprocess(data):
result = heavy_computation(data)
results.append(result) # ⚠️ 永远不清理!
2. 闭包或回调函数持有外部引用
事件驱动框架里尤其常见:
defcreate_handler(obj):
defhandler():
print(obj.value) # obj 被闭包牢牢抓住
return handler
3. 第三方库的“黑箱”行为
比如某些 ORM 或日志库内部缓存未释放,你以为删了对象,其实它还在。
别猜了,用工具“照妖镜”一照便知!
tracemalloc 是 Python 3.4+ 自带的神器,能追踪内存分配源头:
import tracemalloc
tracemalloc.start()
# 你的可疑代码
run_suspicious_function()
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')
for stat in top_stats[:5]:
print(stat)
输出可能长这样:
/path/to/leaky.py:23: size=128 MiB, count=1000000
直接定位到第23行——就是它!
更狠的招:用 memory_profiler 看每行内存变化
先安装:
pip install memory-profiler psutil
然后加个装饰器:
from memory_profiler import profile
@profile
defleaky_function():
data = []
for i in range(100000):
data.append("x" * 100) # 每次都吃100字节
return len(data)
运行:
python -m memory_profiler your_script.py
你会看到每一行代码执行后的内存增量,像心电图一样清晰 💓:
Line # Mem usage Increment Line Contents
================================================
5 15.2 MiB 15.2 MiB @profile
6 def leaky_function():
7 15.2 MiB 0.0 MiB data = []
8 92.4 MiB 77.2 MiB for i in range(100000):
9 92.4 MiB 77.2 MiB data.append("x" * 100)
第8-9行吃掉77MB?罪证确凿!
实战案例:一个“看似无害”的缓存函数
我曾写过这样的装饰器:
_cache = {}
defcached(func):
defwrapper(*args):
if args in _cache:
return _cache[args]
result = func(*args)
_cache[args] = result # ⚠️ 永不淘汰!
return result
return wrapper
短期看性能飞起,长期跑?内存稳步上涨!
解决方案?用 LRU 缓存!
from functools import lru_cache
@lru_cache(maxsize=128)
defexpensive_func(x):
return x ** 2
自动淘汰旧项,内存稳如老狗 🐕。
循环引用?weakref 来救场!
两个对象互相引用,GC 可能无法回收:
classParent:
def__init__(self):
self.child = Child(self)
classChild:
def__init__(self, parent):
self.parent = parent # 🔁 循环!
改用弱引用打破僵局:
import weakref
classChild:
def__init__(self, parent):
self.parent = weakref.ref(parent) # 不增加引用计数
现在 GC 能顺利回收它们了。
最后一句大实话
Python 不是“写了就跑”的玩具语言。
内存管理不当,再优雅的代码也会变成“内存吞噬怪兽”。
别等线上报警才慌——定期用 tracemalloc 或 memory_profiler 扫一遍,
就像体检一样,早发现,早治疗。
毕竟,省下的不只是内存,还有你的头发和年终奖 💸。