目录
[Python 垃圾回收触发完整机制](#Python 垃圾回收触发完整机制)
[什么情况会让 refcnt-1,可能触发回收](#什么情况会让 refcnt-1,可能触发回收)
[二、第二套:分代标记清除 GC(专门解决循环引用)](#二、第二套:分代标记清除 GC(专门解决循环引用))
[1. 自动触发(最常用,程序后台自动执行)](#1. 自动触发(最常用,程序后台自动执行))
[阈值读取 / 修改](#阈值读取 / 修改)
[2. 手动强制触发(代码主动调用)](#2. 手动强制触发(代码主动调用))
[3. 程序退出时强制触发](#3. 程序退出时强制触发)
[三、特殊场景:GC 不会触发 / 暂时失效](#三、特殊场景:GC 不会触发 / 暂时失效)
[Python 垃圾回收机制完整讲解:原理 + 手动使用 + 实操代码](#Python 垃圾回收机制完整讲解:原理 + 手动使用 + 实操代码)
[1. 基础:引用计数(实时回收)](#1. 基础:引用计数(实时回收))
[2. 补充:分代标记清除(解决循环引用)](#2. 补充:分代标记清除(解决循环引用))
[二、如何手动使用垃圾回收(gc 标准库)](#二、如何手动使用垃圾回收(gc 标准库))
[1. 导入模块](#1. 导入模块)
[2. 常用 API(实操)](#2. 常用 API(实操))
[① 手动执行一次全量垃圾回收](#① 手动执行一次全量垃圾回收)
[② 查看 / 修改分代回收阈值(控制自动触发时机)](#② 查看 / 修改分代回收阈值(控制自动触发时机))
[③ 关闭 / 开启自动垃圾回收](#③ 关闭 / 开启自动垃圾回收)
[④ 查看当前待回收的循环引用对象](#④ 查看当前待回收的循环引用对象)
[⑤ del 手动减少引用(配合 GC 使用)](#⑤ del 手动减少引用(配合 GC 使用))
[四、开发中正确使用 GC 的场景](#四、开发中正确使用 GC 的场景)
[场景 1:大量循环引用对象,内存暴涨,手动清理](#场景 1:大量循环引用对象,内存暴涨,手动清理)
[场景 2:调试内存泄漏](#场景 2:调试内存泄漏)
[场景 3:程序退出前释放全部内存](#场景 3:程序退出前释放全部内存)
[六、补充:C/C++ 嵌入 Python 时的回收(适配你的 Qt+C++ OpenCV 栈)](#六、补充:C/C++ 嵌入 Python 时的回收(适配你的 Qt+C++ OpenCV 栈))
Python的垃圾回收机制是如何触发的?
Python 垃圾回收触发完整机制
Python GC 分为两套体系:引用计数(即时触发) 、分代标记清除 GC(阈值自动触发 / 手动触发),两套触发逻辑完全分开。
一、第一套:引用计数回收(实时、无额外触发条件)
对应底层源码 PyObject.ob_refcnt
触发规则
只要对象的引用计数 ob_refcnt 递减到 0 ,立刻同步触发回收,不需要等待、不需要 GC 扫描线程。 会自动释放该对象内存。
什么情况会让 refcnt-1,可能触发回收
- 变量被
del x删除; - 局部变量离开作用域(函数执行完毕、循环结束);
- 变量重新赋值:
a = 1; a = 2,旧对象1引用 - 1; - 容器(列表 / 字典)移除元素、容器销毁;
- 程序运行结束,所有变量全部销毁。
局限性
循环引用场景下,两个对象互相持有引用,refcnt 永远≥1,引用计数无法触发回收,必须靠第二套分代 GC 兜底。
二、第二套:分代标记清除 GC(专门解决循环引用)
Python 把对象分为 3 代:0代(新生代)、1代、2代(老年代),越老扫描频率越低。
1. 自动触发(最常用,程序后台自动执行)
核心:每代维护对象计数阈值,超出阈值自动触发对应代回收。
阈值读取 / 修改
python
import gc
print(gc.get_threshold()) # 输出 (threshold0, threshold1, threshold2)
# 默认:(700, 10, 10)
参数含义:
threshold0 = 7000 代新建对象数量达到 700,触发 0 代 GC 扫描; 每次成功完成 0 代回收,会记录一次计数;累计满 10 次,0 代对象晋升到 1 代,触发 1 代扫描。threshold1 = 101 代累计满 10 次回收,对象晋升到 2 代,触发 1 代完整扫描;threshold2 = 102 代累计满 10 次晋升,触发全代完整 GC 扫描(开销最大)。
自动触发完整流程
- 创建新对象时,解释器会给 0 代计数器 + 1;
- 0 代计数 ≥700 → 触发 0 代 GC:
- 扫描所有 0 代对象,找出循环引用、标记存活对象;
- 销毁无存活引用的垃圾对象;
- 存活对象晋升到 1 代,0 代回收计数 + 1;
- 0 代回收次数 ≥10 → 触发 1 代 GC,存活对象晋升 2 代;
- 1 代回收次数 ≥10 → 触发全代 GC(0+1+2 代全部扫描)。
2. 手动强制触发(代码主动调用)
不管当前计数是否达到阈值,立刻执行全代垃圾扫描:
python
import gc
# 立刻执行完整GC,返回本次回收的对象数量
gc.collect()
# 只回收指定代(0/1/2)
gc.collect(0) # 仅扫描0代
gc.collect(1) # 扫描0+1代
gc.collect(2) # 全代扫描,等同于无参collect()
适用场景:处理大量图像、YOLO 张量、内存泄漏调试时手动释放内存。
3. 程序退出时强制触发
Python 脚本 / 解释器关闭时,自动执行一次完整 GC,释放所有残留循环引用对象,避免内存残留。
三、特殊场景:GC 不会触发 / 暂时失效
-
手动关闭自动 GC
python
gc.disable()关闭后,分代 GC 不会自动触发,仅靠引用计数回收;循环引用对象会永久占用内存,直到程序结束。 恢复自动:
gc.enable() -
存在
__del__析构函数的循环引用对象 GC 无法判断析构执行顺序,这类对象不会被自动回收,会存入gc.garbage列表永久滞留。 -
多线程 GIL 限制 GC 扫描全程需要持有 GIL 锁,线程密集场景会延迟 GC 触发时机。
四、触发逻辑极简总结表
表格
| 回收类型 | 触发方式 | 触发时机 | 处理对象 |
|---|---|---|---|
| 引用计数回收 | 自动即时触发 | 对象 refcnt == 0 | 无循环引用的普通对象(int、float、无嵌套容器) |
| 分代 GC 自动触发 | 阈值触发 | 对应代新建对象 / 晋升次数达到阈值 | 存在循环引用的容器(list、dict、自定义类实例) |
| 分代 GC 手动触发 | 代码调用gc.collect() |
代码执行到该行时 | 全量循环引用垃圾,随时释放 |
| 全局收尾 GC | 程序退出自动触发 | 解释器关闭最后阶段 | 全部未释放的残留对象 |
如何使用Python的垃圾回收机制?
Python 垃圾回收机制完整讲解:原理 + 手动使用 + 实操代码
一、底层核心:两套回收机制(自动运行,也可手动调用)
1. 基础:引用计数(实时回收)
对应你刚才看的源码 PyObject->ob_refcnt
- 规则
- 对象被赋值、传入函数:
ob_refcnt +=1 - 变量销毁、函数退出、del 删除、程序结束:
ob_refcnt -=1 ob_refcnt == 0:立刻释放对象内存
- 对象被赋值、传入函数:
- 缺陷:循环引用无法回收
python
# 循环引用案例,引用计数无法归零
a = []
b = []
a.append(b)
b.append(a)
del a, b
# 此时a、b互相引用,refcnt都是1,引用计数无法释放
2. 补充:分代标记清除(解决循环引用)
专门处理循环引用对象,Python 分 3 代(0/1/2),新对象放 0 代,扫描阈值触发自动回收。
二、如何手动使用垃圾回收(gc 标准库)
1. 导入模块
python
import gc
2. 常用 API(实操)
① 手动执行一次全量垃圾回收
python
# 返回本次回收释放的对象数量
released = gc.collect()
print("本次清理无效对象:", released)
② 查看 / 修改分代回收阈值(控制自动触发时机)
python
# 获取三代阈值 (threshold0, threshold1, threshold2)
print(gc.get_threshold())
# 修改阈值:0代满500个对象就触发扫描
gc.set_threshold((500, 10, 5))
③ 关闭 / 开启自动垃圾回收
python
gc.disable() # 关闭自动GC,所有垃圾只靠引用计数
gc.enable() # 恢复自动GC
gc.isenabled() # 返回True/False,查看自动GC是否开启
④ 查看当前待回收的循环引用对象
python
gc.collect()
# 保存未释放、存在循环引用的对象列表
garbage_list = gc.garbage
print("残留垃圾对象:", garbage_list)
⑤ del 手动减少引用(配合 GC 使用)
del 变量名 作用:删除变量,对象引用计数 - 1,不会立刻释放内存,仅当 refcnt=0 才释放。
python
s = [1,2,3]
del s # 变量s销毁,列表refcnt-1,若无其他引用直接释放
三、完整实操示例(循环引用手动回收)
python
import gc
# 制造循环引用
a = []
b = []
a.append(b)
b.append(a)
# 删除变量,此时两个列表互相引用,refcnt=1
del a, b
# 手动触发垃圾回收
clear_num = gc.collect()
print(f"清理了{clear_num}个循环引用对象")
# 查看残留垃圾
print("残留垃圾:", gc.garbage)
四、开发中正确使用 GC 的场景
场景 1:大量循环引用对象,内存暴涨,手动清理
处理图像、深度学习张量、大规模列表嵌套时,循环引用堆积,定期 gc.collect() 释放内存。
场景 2:调试内存泄漏
gc.disable()关闭自动回收- 运行业务代码
gc.collect()手动回收- 打印
gc.garbage定位无法释放的对象,排查循环引用。
场景 3:程序退出前释放全部内存
脚本结束前执行一次 gc.collect(),主动清空内存,避免系统延迟释放。
五、关键注意点
- 绝大多数场景不用手动调用 gc Python 自动运行 GC,手动
gc.collect()仅在内存泄漏、大数据场景优化使用;频繁调用会拖慢运行速度。 - 循环引用中,带__del__析构函数的对象回收会失效 自定义类写了
__del__方法,出现循环引用时 GC 无法判断析构顺序,会永久存放在gc.garbage中无法释放。 - 引用计数是主线,GC 只是兜底优先避免循环引用,不要依赖手动 GC 解决代码设计问题。
六、补充:C/C++ 嵌入 Python 时的回收(适配你的 Qt+C++ OpenCV 栈)
如果你在 VS2015 C++ 代码嵌入 Python 解释器:
- C 层 API 手动回收:
PyGC_Collect() - 退出解释器前必须执行垃圾回收,否则 Python 堆内存泄漏,影响 Qt 程序长期运行。