Python垃圾回收机制详解

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. 实际应用建议

  1. 避免循环引用 :使用弱引用(weakref模块)或重新设计数据结构
  2. 及时释放大对象 :使用del显式删除不再需要的大对象
  3. 谨慎使用__del____del__方法可能影响垃圾回收
  4. 监控内存使用 :使用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()
相关推荐
不易思不逸24 分钟前
SAM2 测试
人工智能·python
JIngJaneIL36 分钟前
基于springboot + vue房屋租赁管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·后端
期待のcode1 小时前
Java的抽象类和接口
java·开发语言
wadesir1 小时前
Go语言中高效读取数据(详解io包的ReadAll函数用法)
开发语言·后端·golang
趣知岛2 小时前
智能家居与物联网项目实战全指南:从架构设计到落地部署
python·物联网·智能家居
小高不明2 小时前
前缀和一维/二维-复习篇
开发语言·算法
龘龍龙2 小时前
Python基础(八)
开发语言·python
幺零九零零3 小时前
Golang-Swagger
开发语言·后端·golang
vibag3 小时前
FastAPI框架
python·pycharm·fastapi
站大爷IP3 小时前
从零开始用Python生成码:自定义样式与Logo嵌入
python