0

0

Python 循环引用导致的内存泄漏分析

冷炫風刃

冷炫風刃

发布时间:2026-02-22 16:49:08

|

112人浏览过

|

来源于php中文网

原创

python循环引用导致内存泄漏,因引用计数无法归零且含__del__方法时gc放弃清理;weakref可破环但需谨慎使用,定位需借助gc模块分析garbage与referrers。

python 循环引用导致的内存泄漏分析

循环引用在 Python 中为什么会导致内存泄漏

Python 主要靠引用计数回收对象,但当两个或多个对象互相持有对方的引用(比如 A.ref = BB.ref = A),它们的引用计数永远不会降到 0,即使外部已无任何变量指向它们。此时 CPython 的循环垃圾收集器(GC)本该介入,但前提是这些对象**没有实现 __del__ 方法**——一旦有,GC 就会放弃清理,因为析构顺序无法安全确定。

  • 常见于自定义类中手动维护双向关系:树节点父子互指、观察者模式中 subject 和 observer 互相保存引用
  • weakref 是最直接的解法,但不是所有场景都适用(比如需要强引用语义时)
  • 显式调用 gc.collect() 可临时验证是否存在待回收的循环,但不能作为常规手段——它开销大、不可预测,且不解决根本问题

如何快速定位疑似循环引用的对象

别等 OOM 才查。用 gc.get_objects() 配合 gc.get_referrers() 可以逆向追踪谁在持有一个对象的引用,再层层剥开。

  • 先禁用 GC:gc.disable(),避免干扰;然后触发可疑操作(如创建一批对象、执行一次回调)
  • gc.collect(0) 强制运行第 0 代收集,再检查 len(gc.garbage) —— 若非零,说明有未处理的循环引用被 GC 放进了 garbage 列表
  • gc.garbage 中的每个对象,调用 gc.get_referrers(obj) 查看谁引用了它;反复下钻,直到找到源头(通常是某个全局容器或长生命周期对象)
  • 注意:gc.get_referrers() 返回结果可能包含帧对象(frame),它们本身也参与循环,需过滤掉 types.FrameType

weakref 的正确用法和典型误用

weakref 不是万能胶布,用错反而让逻辑出 bug 或掩盖问题。

畅图
畅图

AI可视化工具

下载
  • weakref.ref(obj) 替代直接赋值,访问前必须先调用返回的弱引用对象(如 ref()),它可能返回 None —— 忘记判空是常见 crash 点
  • 对容器类(如 listdict)存弱引用时,别直接存 weakref.ref(obj),而要用 weakref.WeakKeyDictionaryweakref.WeakValueDictionary,它们自动处理 key/value 失效逻辑
  • 不要对不可变对象(如 intstrtuple)用 weakref —— 它们可能被驻留(interned),生命周期不由引用计数控制,弱引用行为不可靠
  • 类方法绑定(bound method)默认强引用实例,若需弱引用,得用 weakref.WeakMethod(Python 3.4+)或手动拆解为 weakref.ref(inst) + 函数对象

哪些情况 GC 根本不会尝试回收

不是所有循环都能进 garbage,有些会被 GC 主动跳过,导致“看不见的泄漏”。

立即学习Python免费学习笔记(深入)”;

  • 对象属于“原子类型”且没自定义 __del__:比如纯 dictlist 构成的循环,GC 能处理,但速度慢、延迟高,尤其在大量小对象时
  • 对象在 __del__ 中又触发新引用(例如日志写入、发通知),可能引发 GC 拒绝清理并静默丢弃该对象
  • 线程局部存储(threading.local)中的对象,若其值构成循环,GC 在非主线程中可能不扫描该线程的栈帧,导致漏收
  • C 扩展模块分配的对象(如 NumPy 数组、Cython 类实例)若未正确实现 tp_traverse / tp_clear,GC 完全感知不到它们是否参与循环

真正难缠的从来不是“能不能检测”,而是“检测到了却不敢动”——比如生产环境里一个带 __del__ 的老模块,改它得通读十年前的 C API 文档。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

810

2023.08.02

int占多少字节
int占多少字节

int占4个字节,意味着一个int变量可以存储范围在-2,147,483,648到2,147,483,647之间的整数值,在某些情况下也可能是2个字节或8个字节,int是一种常用的数据类型,用于表示整数,需要根据具体情况选择合适的数据类型,以确保程序的正确性和性能。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

579

2024.08.29

c++怎么把double转成int
c++怎么把double转成int

本专题整合了 c++ double相关教程,阅读专题下面的文章了解更多详细内容。

274

2025.08.29

C++中int的含义
C++中int的含义

本专题整合了C++中int相关内容,阅读专题下面的文章了解更多详细内容。

210

2025.08.29

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

421

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

595

2023.08.10

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

695

2023.08.10

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

695

2023.08.10

pixiv网页版官网登录与阅读指南_pixiv官网直达入口与在线访问方法
pixiv网页版官网登录与阅读指南_pixiv官网直达入口与在线访问方法

本专题系统整理pixiv网页版官网入口及登录访问方式,涵盖官网登录页面直达路径、在线阅读入口及快速进入方法说明,帮助用户高效找到pixiv官方网站,实现便捷、安全的网页端浏览与账号登录体验。

1030

2026.02.13

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 22.4万人学习

Django 教程
Django 教程

共28课时 | 4.5万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.7万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号