
核心价值:彻底拆解 Python 引用计数、垃圾回收的底层机制、触发条件、调优策略,解决 "内存泄漏"、"GC 卡顿" 等生产级问题。
你是否有过这样的疑问:
free()?这些问题的答案,都在于 Python 的自动内存管理机制—— 它由两部分组成:
这两种机制相互配合,保证了 Python 程序的内存安全和效率。本文将从底层原理、代码验证、调优策略三个维度,彻底解析 Python 的内存管理。
引用计数是 Python 最基础、最核心的内存管理机制。每个 Python 对象都有一个引用计数计数器,记录当前有多少个引用指向该对象。当计数器为0时,对象立即被回收,内存被释放。
操作类型 | 引用计数 + 1 | 引用计数 - 1 |
|---|---|---|
赋值操作 | x = obj | del x |
函数传参 | func(obj) | 函数执行完毕 |
添加到容器 | lst.append(obj) | lst.remove(obj) |
作为容器元素 | tuple(obj) | 容器被回收 |
可以用sys.getrefcount()函数查看对象的引用计数:
getrefcount()本身会增加对象的引用计数(因为它将对象作为参数传递),因此实际引用计数是返回值 - 1。代码验证 1:基本引用计数
import sys
# 创建对象,引用计数=1
x = "hello"
print(f"x的引用计数:{sys.getrefcount(x) - 1}") # 1
# 赋值操作,引用计数+1 → 2
y = x
print(f"x的引用计数:{sys.getrefcount(x) - 1}") # 2
# 添加到列表,引用计数+1 → 3
lst = [x]
print(f"x的引用计数:{sys.getrefcount(x) - 1}") # 3
# 删除y,引用计数-1 → 2
del y
print(f"x的引用计数:{sys.getrefcount(x) - 1}") # 2
# 从列表中删除,引用计数-1 → 1
lst.remove(x)
print(f"x的引用计数:{sys.getrefcount(x) - 1}") # 1
# 删除x,引用计数-1 → 0
del x
# print(f"x的引用计数:{sys.getrefcount(x) - 1}") # 报错:x未定义,对象已被回收代码验证 2:函数传参的引用计数
import sys
def func(obj):
print(f"函数内obj的引用计数:{sys.getrefcount(obj) - 1}") # 2(函数内1个引用+getrefcount的临时引用)
x = "hello"
print(f"函数外x的引用计数:{sys.getrefcount(x) - 1}") # 1
func(x)
print(f"函数外x的引用计数:{sys.getrefcount(x) - 1}") # 1(函数执行完毕,引用计数恢复)优点:
缺点:
当两个或多个对象相互引用,且没有其他外部引用指向它们时,就会形成循环引用。此时每个对象的引用计数都至少为 1,因此引用计数机制无法回收这些对象,导致内存泄漏。
代码验证:循环引用导致内存泄漏
import sys
import gc
# 关闭自动GC,方便观察
gc.disable()
class Node:
def __init__(self):
self.next = None
# 创建两个节点,形成循环引用
a = Node()
b = Node()
a.next = b
b.next = a
# 查看引用计数
print(f"a的引用计数:{sys.getrefcount(a) - 1}") # 2(a本身+next引用)
print(f"b的引用计数:{sys.getrefcount(b) - 1}") # 2(b本身+next引用)
# 删除外部引用
del a
del b
# 此时节点a和b已无外部引用,但由于循环引用,引用计数仍为1
# 手动调用GC
gc.collect()
# 查看未回收的对象数量(假设只有这两个节点)
print(f"未回收的对象数量:{len(gc.garbage)}") # 0(手动GC已回收)a = [b],b = [a]。代码验证:嵌套列表循环引用
import sys
import gc
gc.disable()
a = []
b = []
a.append(b)
b.append(a)
print(f"a的引用计数:{sys.getrefcount(a) - 1}") # 2
print(f"b的引用计数:{sys.getrefcount(b) - 1}") # 2
del a
del b
# 循环引用,引用计数不为0
gc.collect()
print(f"未回收的对象数量:{len(gc.garbage)}") # 0为了解决循环引用的问题,Python 引入了分代垃圾回收机制,通过定期扫描内存,识别并回收循环引用的对象。
分代垃圾回收基于 **"对象存活时间越长,越不可能被回收"** 的统计规律,将对象分为 3 代:
import gc
import sys
# 查看默认阈值
print(f"第0代阈值:{gc.get_threshold()[0]}") # 700
print(f"第1代阈值:{gc.get_threshold()[1]}") # 10
print(f"第2代阈值:{gc.get_threshold()[2]}") # 10
# 手动设置阈值
gc.set_threshold(1000, 20, 20)
print(f"修改后第0代阈值:{gc.get_threshold()[0]}") # 1000
# 创建对象,触发第0代GC
for i in range(1000):
obj = object()
print(f"第0代GC执行次数:{gc.get_count()[0]}") # 0(触发后重置为0)
print(f"第1代GC执行次数:{gc.get_count()[1]}") # 1(累计1次第0代GC)Python 的垃圾回收采用标记 - 清除算法,分为两个阶段:
代码验证:标记 - 清除算法
import gc
import sys
gc.disable()
# 创建循环引用
a = []
b = []
a.append(b)
b.append(a)
# 标记-清除
gc.collect()
print(f"未回收的对象:{len(gc.garbage)}") # 0内存泄漏是指不再使用的对象仍然占据内存,导致程序占用的内存越来越大,最终可能导致系统崩溃。
gc模块可以用gc模块的gc.garbage属性查看未回收的对象,用gc.collect()手动触发 GC:
import gc
gc.enable()
gc.set_debug(gc.DEBUG_LEAK) # 开启泄漏调试
# 创建循环引用
class Node:
def __init__(self):
self.next = None
a = Node()
b = Node()
a.next = b
b.next = a
del a
del b
gc.collect()
print(f"泄漏的对象:{gc.garbage}") # 输出泄漏的对象tracemalloc模块tracemalloc模块用于跟踪内存分配,可以查看程序在哪个位置分配了最多的内存:
import tracemalloc
# 开始跟踪
tracemalloc.start()
# 模拟内存泄漏
leak_list = []
for i in range(10000):
leak_list.append("x" * 1024) # 分配1KB内存
# 查看内存使用情况
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics("lineno")
print(f"前5个内存分配位置:")
for stat in top_stats[:5]:
print(stat)运行结果示例:
前5个内存分配位置:
test.py:9: size=9840 KiB, count=9773, average=1024 Bweakref模块创建弱引用:弱引用不会增加对象的引用计数,对象被回收后,弱引用自动失效。代码验证:weakref 弱引用
import sys
import weakref
class Node:
def __init__(self):
self.data = "test"
a = Node()
b = weakref.ref(a) # 创建弱引用
print(f"a的引用计数:{sys.getrefcount(a) - 1}") # 1
print(f"b()的内容:{b()}") # <__main__.Node object at 0x000001>
# 删除a,对象被回收
del a
print(f"b()的内容:{b()}") # None(对象已被回收)代码验证:全局变量与局部变量
# 全局变量:长期存在
global_list = []
def func():
# 局部变量:函数执行完毕后自动回收
local_list = []
for i in range(1000):
local_list.append("test")
func()functools.lru_cache装饰器实现自动过期的缓存。代码验证:lru_cache 自动过期
from functools import lru_cache
# 缓存最近5个调用结果
@lru_cache(maxsize=5)
def fib(n):
if n <= 1:
return n
return fib(n-1) + fib(n-2)
print(fib(10)) # 55
print(fib.cache_info()) # CacheInfo(hits=8, misses=11, maxsize=5, currsize=5)with语句)自动关闭资源。代码验证:上下文管理器自动关闭
# 错误:忘记关闭文件
f = open("test.txt", "w")
f.write("test")
# f.close() # 忘记关闭
# 正确:上下文管理器自动关闭
with open("test.txt", "w") as f:
f.write("test")
# 文件自动关闭代码验证:调整 GC 阈值
import gc
# 获取当前阈值
print(f"当前阈值:{gc.get_threshold()}") # (700, 10, 10)
# 调整阈值
gc.set_threshold(1000, 20, 20)
print(f"调整后阈值:{gc.get_threshold()}") # (1000, 20, 20)Python 的内存管理机制是一个高效、自动的系统,它由引用计数和分代垃圾回收两部分组成:
为了写出高效、无内存泄漏的 Python 代码,你需要:
希望这篇指南能让你彻底搞懂 Python 的内存管理机制,写出更高效、更稳定的 Python 代码。