如何在 Python 中管理内存?
Python 内存管理机制
Python 的内存管理由解释器自动处理,核心机制有三层:
① 引用计数(Reference Counting) ← 主要机制
② 垃圾回收(Garbage Collection) ← 处理循环引用
③ 内存池(Memory Pool) ← 小对象复用,提升性能
① 引用计数
每个对象都维护一个引用计数器,当计数降为 0 时立即释放内存。
import sys
a = [1, 2, 3]
print(sys.getrefcount(a)) # 2(a 本身 + getrefcount 的参数)
b = a # 引用计数 +1
print(sys.getrefcount(a)) # 3
del b # 引用计数 -1
print(sys.getrefcount(a)) # 2
del a # 引用计数降为 0,内存释放
引用计数增加的场景:赋值、传参、添加到容器、sys.getrefcount() 调用本身。
② 垃圾回收:处理循环引用
引用计数无法处理循环引用(两个对象互相引用,计数永不为 0):
import gc
class Node:
def __init__(self):
self.ref = None
a = Node()
b = Node()
a.ref = b # a 引用 b
b.ref = a # b 引用 a
del a
del b
# 此时 a、b 引用计数均为 1,不会被引用计数释放
# Python 的分代垃圾回收器会检测并清理
Python 使用分代垃圾回收(Generational GC),将对象分为三代:
| 代 | 含义 | 触发频率 |
|---|---|---|
| 第 0 代 | 新创建的对象 | 最频繁 |
| 第 1 代 | 经历过一次 GC 的对象 | 中等 |
| 第 2 代 | 长期存活的对象 | 最少 |
import gc
gc.collect() # 手动触发垃圾回收
print(gc.get_count()) # (第0代, 第1代, 第2代) 对象数量
print(gc.get_threshold()) # (700, 10, 10) 触发阈值
gc.disable() # 禁用 GC(极少数高性能场景)
gc.enable() # 重新启用
③ 内存池(pymalloc)
CPython 对小于 512 字节的对象使用内存池(pymalloc),避免频繁向操作系统申请/释放内存:
- 小整数(-5 ~ 256)全局缓存,永久复用
- 短字符串驻留(interning),同值共享同一对象
- 内存池按块分配,释放后归还池中而非操作系统
# 小整数缓存
a = 100
b = 100
print(a is b) # True ← 同一个缓存对象
a = 1000
b = 1000
print(a is b) # False ← 超出缓存范围
内存管理最佳实践
1. 用 with 语句及时释放资源
# ✅ 推荐:with 块结束自动关闭文件,释放文件描述符
with open("data.txt") as f:
data = f.read()
# ❌ 不推荐:依赖 GC 释放
f = open("data.txt")
data = f.read()
# 忘记调用 f.close()
2. 处理大数据用生成器替代列表
# ❌ 一次性加载到内存
data = [process(line) for line in open("huge.log")]
# ✅ 惰性处理,内存占用固定
def process_lines(path):
with open(path) as f:
for line in f:
yield process(line)
3. 用 __slots__ 减少实例内存占用
class WithDict:
def __init__(self, x, y):
self.x = x
self.y = y
class WithSlots:
__slots__ = ["x", "y"] # 禁用 __dict__,改用固定数组
def __init__(self, x, y):
self.x = x
self.y = y
import sys
print(sys.getsizeof(WithDict(1, 2))) # ~48 字节(+ __dict__ ~232 字节)
print(sys.getsizeof(WithSlots(1, 2))) # ~56 字节(无 __dict__)
4. 显式删除不再需要的大对象
import gc
big_data = load_large_dataset()
process(big_data)
del big_data # 显式删除
gc.collect() # 必要时手动触发 GC
5. 使用 weakref 避免循环引用
import weakref
class Parent:
def __init__(self):
self.child = None
class Child:
def __init__(self, parent):
self.parent = weakref.ref(parent) # 弱引用,不增加引用计数
p = Parent()
c = Child(p)
p.child = c
# 删除 p 时不会产生循环引用问题
内存分析工具
# 查看对象引用计数
import sys
sys.getrefcount(obj)
# 查看对象内存大小(不含子对象)
sys.getsizeof(obj)
# 查看所有存活对象
import gc
gc.get_objects()
# 第三方工具:内存详细分析
# pip install memory-profiler
# @profile 装饰器逐行分析内存
总结
| 机制 | 作用 | 触发时机 |
|---|---|---|
| 引用计数 | 对象归零立即回收 | 自动、实时 |
| 分代 GC | 回收循环引用 | 阈值触发或手动 |
| 内存池 | 小对象高效复用 | 自动、透明 |
- Python 内存管理基本全自动,日常开发无需手动干预
- 大内存场景:优先使用生成器、
__slots__、weakref - 有内存泄漏嫌疑时:用
gc模块和memory-profiler定位