Python的垃圾回收机制
Python使用自动内存管理 ,主要通过引用计数 和分代回收两种机制来实现垃圾回收。
1. 引用计数(Reference Counting)
基本概念
- 每个Python对象都有一个引用计数,表示有多少引用指向该对象
- 当引用计数为0时,对象会被立即回收
引用计数变化规则:
python
import sys
# 创建对象,引用计数为1
a = [1, 2, 3] # 引用计数 = 1
print(f"初始引用计数: {sys.getrefcount(a)}") # 注意:getrefcount()会增加临时引用
b = a # 引用计数 +1,变为2
print(f"赋值后引用计数: {sys.getrefcount(a)}")
c = [a, a] # 列表中有两个引用,引用计数 +2,变为4
print(f"放入容器后引用计数: {sys.getrefcount(a)}")
# 删除引用
del b # 引用计数 -1,变为3
del c[0] # 引用计数 -1,变为2
del c # 删除整个列表,引用计数 -2(因为列表中有a的引用),变为0,对象被回收
引用计数的优缺点:
优点:
- 实时性高:对象不再被引用时立即释放
- 简单高效:大多数情况下开销小
缺点:
- 循环引用问题:两个或多个对象相互引用,导致引用计数永不为0
- 额外的内存开销:需要存储引用计数
2. 分代回收(Generational GC)
解决循环引用问题
分代回收是引用计数的补充,专门处理循环引用。
分代原理:
- 将对象分为三代:0代、1代、2代
- 新创建的对象进入0代
- 对象存活时间越长,代数越高
- 回收频率:0代 > 1代 > 2代
工作机制:
python
import gc
# 创建循环引用示例
def create_cycle():
a = [1, 2, 3] # 对象A
b = [4, 5, 6] # 对象B
a.append(b) # A引用B
b.append(a) # B引用A,形成循环引用
# 离开函数后,a和b的局部引用消失,但对象间仍相互引用
# 引用计数不会为0,需要分代回收处理
# 启用/禁用垃圾回收
gc.enable() # 默认启用
gc.disable() # 临时禁用
gc.enable()
# 手动触发垃圾回收
collected = gc.collect() # 返回回收的对象数量
print(f"回收了 {collected} 个对象")
# 获取分代回收统计信息
print(gc.get_stats())
分代回收算法:
python
import gc
# 查看和设置各代阈值
print(f"GC阈值: {gc.get_threshold()}")
# 输出类似 (700, 10, 10)
# 第一个值:0代触发回收的阈值(分配的对象数 - 释放的对象数)
# 第二个值:0代回收次数达到该值时触发1代回收
# 第三个值:1代回收次数达到该值时触发2代回收
# 设置自定义阈值
gc.set_threshold(800, 10, 10)
# 获取各代对象数量
print(f"各代对象数量: {gc.get_count()}")
3. 完整示例
python
import gc
import sys
class Node:
def __init__(self, name):
self.name = name
self.next = None
print(f"创建节点: {self.name}")
def __del__(self):
print(f"销毁节点: {self.name}")
# 演示引用计数
def demo_reference_counting():
print("\n=== 引用计数演示 ===")
obj1 = Node("对象1") # 引用计数 = 1
obj2 = obj1 # 引用计数 = 2
print(f"引用计数: {sys.getrefcount(obj1)-1}")
del obj2 # 引用计数 = 1
del obj1 # 引用计数 = 0,立即销毁
# 演示循环引用
def demo_circular_reference():
print("\n=== 循环引用演示 ===")
gc.disable() # 暂时禁用分代回收
a = Node("A")
b = Node("B")
a.next = b # A引用B
b.next = a # B引用A,形成循环引用
# 删除外部引用
del a
del b
# 此时两个对象相互引用,引用计数各为1
# 但由于分代回收被禁用,它们不会被自动回收
gc.enable() # 重新启用分代回收
collected = gc.collect() # 手动触发回收
print(f"回收了 {collected} 个对象")
# 演示分代回收
def demo_generational_gc():
print("\n=== 分代回收演示 ===")
# 创建大量对象
objects = []
for i in range(1000):
obj = Node(f"临时对象{i}")
objects.append(obj)
# 查看GC统计
print(f"GC计数: {gc.get_count()}")
# 删除引用
del objects
# 触发垃圾回收
collected = gc.collect()
print(f"回收了 {collected} 个对象")
# 演示弱引用(避免循环引用)
import weakref
def demo_weak_reference():
print("\n=== 弱引用演示 ===")
class Data:
def __init__(self, value):
self.value = value
obj = Data(100)
weak_ref = weakref.ref(obj) # 创建弱引用
print(f"通过弱引用访问: {weak_ref()}")
print(f"弱引用指向的对象: {weak_ref().value}")
del obj # 删除原始对象
print(f"对象删除后弱引用: {weak_ref()}") # 返回None
if __name__ == "__main__":
demo_reference_counting()
demo_circular_reference()
demo_generational_gc()
demo_weak_reference()
4. 实际应用建议
- 避免循环引用 :使用弱引用(
weakref模块)或重新设计数据结构 - 及时释放大对象 :使用
del显式删除不再需要的大对象 - 谨慎使用
__del__:__del__方法可能影响垃圾回收 - 监控内存使用 :使用
tracemalloc模块追踪内存分配
python
# 监控内存使用示例
import tracemalloc
tracemalloc.start()
# 执行一些代码
data = [i for i in range(100000)]
# 获取内存快照
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')
print("\n内存分配排行:")
for stat in top_stats[:5]:
print(stat)
tracemalloc.stop()