京公网安备 11010802034615号
经营许可证编号:京B2-20210330
Python内存管理方式和垃圾回收算法解析
在列表,元组,实例,类,字典和函数中存在循环引用问题。有 __del__ 方法的实例会以健全的方式被处理。给新类型添加GC支持是很容易的。支持GC的Python与常规的Python是二进制兼容的。
分代式回收能运行工作(目前是三个分代)。由 pybench 实测的结果是大约有百分之四的开销。实际上所有的扩展模块都应该依然如故地正常工作(我不得不修改了标准发行版中的 new 和 cPickle 模块)。一个叫做 gc 的新模块马上就可以用来调试回收器和设置调试选项。
回收器应该是跨平台可移植的。Python 的补丁版本通过了所有的回归测试并且跑 Grail、Idle 和 Sketch 的时候没有任何问题。
自 Python 2.0 和之后的版本,可移植的垃圾回收机制已经包括在其中了。垃圾回收默认是开启的。请高兴些吧!
为什么我们需要垃圾回收?
目前版本的 Python 采用引用计数的方式来管理分配的内存。Python 的每个对象都有一个引用计数,这个引用计数表明了有多少对象在指向它。当这个引用计数为 0 时,该对象就释放了。引用计数对于多数程序都工作地很好。然而,引用计数有一个本质上的缺陷,是由于循环引用引起的。循环引用最简单的例子就是一个引用自身的对象。比如:
>>> l = []
>>> l.append(l)
>>> del l
这个创建的列表的引用计数现在是 1。然而,因为它从 Python 内部已经无法访问,并且可能没法再被用到了,它应该被当作垃圾。在目前版本的 Python 中,这个列表永远不会被释放。
一般情况下循环引用不是一个好的编程实践,并且几乎总该被避免。然而,有时候很难避免制造循环引用,要么则是程序员甚至没有察觉到循环引用的问题。对于长期运行的程序,比如服务器,这个问题特别令人烦恼。人们可不想他们的服务器因为循环引用无法释放访问不到的对象而耗尽内存。对于大型程序,很难发现循环引用是怎么创造出来的。
“传统的”垃圾回收是怎样的?
传统的垃圾回收(比如标记-清除法或者停止-拷贝法)通常工作如下:
找到系统的根对象。根对象就像是全局的环境(比如 Python 中的 __main__ 模块)和堆栈上的对象。
从这些对象搜索所有的可以访问的对象。这些对象都是“活跃”的。
释放其他所有对象。
不幸的是这个方法不能用于当前版本的 Python。由于扩展模块的工作方式,Python 不能完全地确定根对象集合。如果根对象集合没法被准确地确定,我们就有释放仍然被引用的对象的风险。即使用其他方式设计扩展模块,也没有可移植的方式来找到当前 C 堆栈上的对象。而且,引用计数提供了一些 Python 程序员已然期待的有关局部性内存引用和终结语义的好处。最好是我们能够找到一个即能使用引用计数,又能够释放循环引用的的办法。
这个方法如何工作?
从概念上讲,这个方法与传统垃圾回收机制相反。这个方法试图去找到所有的不可访问对象,而不是去找所有的可访问对象。这样更加安全,因为如果这个算法失败了,起码不会比不进行垃圾回收还要糟(不考虑我们浪费掉的时间和空间)。
因为我们仍然在用引用计数,垃圾回收器只需要找到循环引用。引用计数会处理其他类型垃圾。首先我们观察到循环引用只能被容器对象创造。容器对象是可以包含其他对象的引用的对象。在Python中,列表、字典、实例、类和元祖都是容器对象的例子。整数和字符串不是容器。通过这个发现,我们意识到非容器对象可以被垃圾回收忽略。这是一个有用的优化因为整数和字符串这样的应该比较轻快。
现在我们的想法是记录所有的容器对象。有几种方法可以做到,然而最好的一种办法是利用双向链表,链表中的对象结构中包含指针字段。这样就可以使对象从集合中快速插入删除,而且不需要额外内存空间分配。当一个容器被创建,它就插入这个集合,被删除时,就从集合中去除。
既然我们能够得到所有的容器对象,我们怎么找到循环引用呢?首先我们往容器对象中添加两个指针外的另一个字段。我们命名这个字段 gc_refs。通过以下几步我们可以找到循环引用:
对每个容器对象,设 gc_refs 的值为对象的引用计数。
对每个容器对象,找到它引用的其他容器对象并把它们的 gc_refs 值减一。
所有的 gc_refs 大于 1 的容器对象是被容器对象集合外的对象所引用的。我们不能释放这些对象,所以我们把这些对象放到另一个集合。
被移走的对象所引用的对象也不能被释放。我们把它们和它们能访问到的对象都从目前集合移走。
在目前集合中的剩下的对象是仅被该集合中对象引用的(也就是说,他们无法被 Python 取到,也就是垃圾)。我们现在可以去释放这些对象。
Finalizer的问题
我们的宏伟计划还有一个问题,就是使用 finalizer 的问题。Finalizer 就是在 Python 中实例的__del__方法。使用引用计数时,Finalizer 工作地不错。当一个对象的引用计数降到 0 的时候,Finalizer 就在对象被释放前调用了。对程序员来说这是直接明了且容易理解的。
垃圾回收的时候,调用 finalizer 就成了一个麻烦的问题,尤其是面对循环引用的问题时。如果在循环引用中的两个对象都有 finalizer,该怎么做?先调用哪个?在调用第一个 finalizer 之后,这个对象无法被释放因为第二个 finalizer 还能取到它。
因为这个问题没有好的解决办法,被有 finalizer 的对象引用的循环是无法释放的。相反的,这些对象被加进一个全局的无法回收垃圾列表中。程序应该总是可以重新编写来避免这个问题。作为最后的手段,程序可以读取这个全局列表并以一种对于当前应用有意义的方式释放这些引用循环。
代价是什么?
就像有些人说的,天底下没有免费的午餐。然而,这种垃圾回收形式是相当廉价的。最大的代价之一是每各容器对象额外需要的三个字的内存空间。还有维护容器集合的开销。对当前版本的垃圾收集器来说,基于 pybench 这个开销大概是速度下降百分之四。
垃圾回收器目前记录对象的三代信息。通过调整参数,垃圾回收花费的时间可以想多小就多小。对一些应用来说,关掉自动垃圾回收并在运行时显式调用也许是有意义的。然而,以默认的垃圾回收参数运行 pybench,垃圾回收花费的时间看起来并不大。显而易见,大量分配容器对象的应用会引起更多的垃圾回收时间。
目前的补丁增加了一个新的配置项来激活垃圾回收器。有垃圾回收器的 Python 与标准 Python 是二进制兼容的。如果这个选项是关闭的,对 Python 解释器的工作就没有影响。
我该怎么使用它?
只要下载目前版本的 Python 就可以了。垃圾回收器已经包括在了 2.0 以后的版本中,并且默认是默认开启的。如果你在用 Python 1.5.2 版,这里有一个也许能工作的老版本的补丁。如果你用的是 Windows 平台,你可以下载一个用来替代的 python15.dll。
Boehm-Demers 保守垃圾回收
这个补丁增加了一些修改到 Python 1.5.2,以使用 Boehm-Demers 保守垃圾回收。但是你必须先打上这个补丁。依然是采用了引用计数。垃圾回收器只释放引用计数没有释放的内存(即循环引用)。这样应该性能最好。你需要:
$ cd Python-1.5.2
$ patch -p1 < ../gc-malloc-cleanup.diff
$ patch -p1 < ../gc-boehm.diff
$ autoconf
$ ./configure --with-gc
这个补丁假设你安装了 libgc.a,使得 -lgc 链接选项可用(/usr/local/lib 也应该可以)。如果你没有这个库,在编译以前下载安装。
目前,这个补丁只在 Linux 上测试过。在其 他Unix 机器上也许也会工作。在我的 Linux 机器上,GC 版本的 Python 通过了所有的回归测试。
总结
以上就是本文关于Python内存管理方式和垃圾回收算法解析的全部内容,希望对大家有所帮助。
数据分析咨询请扫描二维码
若不方便扫码,搜微信号:CDAshujufenxi
CDA数据分析师 出品 作者:李诗怡 1. 销售漏斗阶段判断 题目:销售漏斗模型中,通过广告、社交媒体等方式触达品牌信息(如浏览品 ...
2026-09-07在Python数据分析中,Pandas库的DataFrame是最核心、最常用的结构化数据表对象,类似于Excel的二维表格,具备规整的行列结构、字 ...
2026-09-07在数据分析、经营复盘、业绩预测与经济统计工作中,平均增速(平均增长率)是衡量数据长期变化趋势、业务发展快慢的核心指标。不 ...
2026-09-07 很多数据分析师精通Excel单元格操作,但当被问到“表结构数据的基本处理单位是什么”“字段和记录的本质区别”“为什么表结 ...
2026-09-07随着大数据技术的快速发展,商业竞争逐步从传统的经验式经营转变为数据驱动的精细化运营。海量的用户行为数据、交易数据、运营数 ...
2026-09-04CDA数据分析师 出品 作者:李诗怡 1. 波士顿矩阵(BCG Matrix) 定义: BCG于1970年提出的业务组合分析工具,以"市场增长率"(纵 ...
2026-09-04 数据分析师八成以上的时间在和数据表格打交道,但许多人拿到Excel后习惯性地先算、先分析,结果回头发现漏了一列关键数据, ...
2026-09-04数据透视表是Excel与Power BI中最核心的数据分析工具,具备快速汇总、维度拆分、动态筛选的能力,可高效完成数据归类与统计展示 ...
2026-09-03在Power BI数据分析可视化场景中,堆积柱状图+折线图是最常用的复合图表组合。堆积柱状图适合展示各细分维度当期数值、结构占比 ...
2026-09-03 很多数据分析师每天与Excel打交道,但当被问到“表格结构数据的基本处理单位是什么”“数据类型误判会引发哪些分析错误”“ ...
2026-09-03CDA数据分析师 出品 作者:李诗怡 一、8个核心数据清洗函数 1. TRIM:一键清除多余空格(最常用) 作用:仅保留文本中"单词/字 ...
2026-09-02数据分析的核心并非单纯操作工具、整理报表或绘制图表,而是依靠科学的思维逻辑挖掘数据价值、解释业务现象、指导经营决策。在完 ...
2026-09-02在社会经济、产业研究、区域治理与大数据实证分析中,面板数据是最具研究价值的数据类型。面板数据同时包含截面维度与时间维度信 ...
2026-09-02 很多数据分析师能熟练计算均值、标准差,但当被问到“如何用一张图让业务方3秒内看懂核心结论”“面对不同数据类型该怎么选 ...
2026-09-02在数据驱动决策的体系中,数据分析按照分析目的可分为描述性分析、诊断性分析、预测性分析与指导性分析四大类型。其中,诊断性分 ...
2026-09-01网络请求是Python爬虫开发、接口测试、数据拉取的核心基础功能,Python生态中主要依靠 urllib 和 requests 两大库实现HTTP请求操 ...
2026-09-01 很多数据分析师面对业务问题时,常常感到“知道要分析,却不知道用什么方法”。其实,数据分析并非无章可循——从三大基础范 ...
2026-09-01在数据库设计与业务数据维护中,自增ID是数据表最常用的主键字段,用于唯一标识每一条业务数据,正常状态下ID应保持连续递增。但 ...
2026-08-31在数理统计、数据分析、经济测算与日常量化评估中,平均值是刻画数据集中趋势、反映整体水平的基础核心指标。在实际应用中,最常 ...
2026-08-31在数据驱动的时代,数据分析早已不是“凭经验、靠感觉”的零散操作,而是一套具备固定逻辑、标准化流程的系统方法——这就是数据 ...
2026-08-31