Python的垃圾回收机制深入分析


Posted in Python onJuly 16, 2014

一、概述:

Python的GC模块主要运用了“引用计数”(reference counting)来跟踪和回收垃圾在引用计数的基础上,还可以通过“标记-清除”(mark and sweep)解决容器对象可能产生的循环引用的问题通过“分代回收”(generation collection)以空间换取时间来进一步提高垃圾回收的效率

二、引用计数

在Python中,大多数对象的生命周期都是通过对象的引用计数来管理的。从广义上来讲,引用计数也是一种垃圾收集机制,而且也是一种最直观,最简单的垃圾收集技术。

原理:当一个对象的引用被创建或者复制时,对象的引用计数加1;当一个对象的引用被销毁时,对象的引用计数减1;当对象的引用计数减少为0时,就意味着对象已经没有被任何人使用了,可以将其所占用的内存释放了。
虽然引用计数必须在每次分配和释放内存的时候加入管理引用计数的动作,然而与其他主流的垃圾收集技术相比,引用计数有一个最大的有点,即“实时性”,任何内存,一旦没有指向它的引用,就会立即被回收。而其他的垃圾收集计数必须在某种特殊条件下(比如内存分配失败)才能进行无效内存的回收。

引用计数机制执行效率问题:引用计数机制所带来的维护引用计数的额外操作与Python运行中所进行的内存分配和释放,引用赋值的次数是成正比的。而这点相比其他主流的垃圾回收机制,比如“标记-清除”,“停止-复制”,是一个弱点,因为这些技术所带来的额外操作基本上只是与待回收的内存数量有关。
如果说执行效率还仅仅是引用计数机制的一个软肋的话,那么很不幸,引用计数机制还存在着一个致命的弱点,正是由于这个弱点,使得侠义的垃圾收集从来没有将引用计数包含在内,能引发出这个致命的弱点就是循环引用(也称交叉引用)。

问题说明:

循环引用可以使一组对象的引用计数不为0,然而这些对象实际上并没有被任何外部对象所引用,它们之间只是相互引用。这意味着不会再有人使用这组对象,应该回收这组对象所占用的内存空间,然后由于相互引用的存在,每一个对象的引用计数都不为0,因此这些对象所占用的内存永远不会被释放。比如:

a = []
b = []
a.append(b)
b.append(a)
print a
[[[…]]]
print b
[[[…]]]

这一点是致命的,这与手动进行内存管理所产生的内存泄露毫无区别。
要解决这个问题,Python引入了其他的垃圾收集机制来弥补引用计数的缺陷:“标记-清除”,“分代回收”两种收集技术。

三、标记-清除

“标记-清除”是为了解决循环引用的问题。可以包含其他对象引用的容器对象(比如:list,set,dict,class,instance)都可能产生循环引用。
我们必须承认一个事实,如果两个对象的引用计数都为1,但是仅仅存在他们之间的循环引用,那么这两个对象都是需要被回收的,也就是说,它们的引用计数虽然表现为非0,但实际上有效的引用计数为0。我们必须先将循环引用摘掉,那么这两个对象的有效计数就现身了。假设两个对象为A、B,我们从A出发,因为它有一个对B的引用,则将B的引用计数减1;然后顺着引用达到B,因为B有一个对A的引用,同样将A的引用减1,这样,就完成了循环引用对象间环摘除。
但是这样就有一个问题,假设对象A有一个对象引用C,而C没有引用A,如果将C计数引用减1,而最后A并没有被回收,显然,我们错误的将C的引用计数减1,这将导致在未来的某个时刻出现一个对C的悬空引用。这就要求我们必须在A没有被删除的情况下复原C的引用计数,如果采用这样的方案,那么维护引用计数的复杂度将成倍增加。

原理:“标记-清除”采用了更好的做法,我们并不改动真实的引用计数,而是将集合中对象的引用计数复制一份副本,改动该对象引用的副本。对于副本做任何的改动,都不会影响到对象生命走起的维护。
这个计数副本的唯一作用是寻找root object集合(该集合中的对象是不能被回收的)。当成功寻找到root object集合之后,首先将现在的内存链表一分为二,一条链表中维护root object集合,成为root链表,而另外一条链表中维护剩下的对象,成为unreachable链表。之所以要剖成两个链表,是基于这样的一种考虑:现在的unreachable可能存在被root链表中的对象,直接或间接引用的对象,这些对象是不能被回收的,一旦在标记的过程中,发现这样的对象,就将其从unreachable链表中移到root链表中;当完成标记后,unreachable链表中剩下的所有对象就是名副其实的垃圾对象了,接下来的垃圾回收只需限制在unreachable链表中即可。

四、分代回收

背景:分代的垃圾收集技术是在上个世纪80年代初发展起来的一种垃圾收集机制,一系列的研究表明:无论使用何种语言开发,无论开发的是何种类型,何种规模的程序,都存在这样一点相同之处。即:一定比例的内存块的生存周期都比较短,通常是几百万条机器指令的时间,而剩下的内存块,起生存周期比较长,甚至会从程序开始一直持续到程序结束
从前面“标记-清除”这样的垃圾收集机制来看,这种垃圾收集机制所带来的额外操作实际上与系统中总的内存块的数量是相关的,当需要回收的内存块越多时,垃圾检测带来的额外操作就越多,而垃圾回收带来的额外操作就越少;反之,当需回收的内存块越少时,垃圾检测就将比垃圾回收带来更少的额外操作。为了提高垃圾收集的效率,采用“空间换时间的策略”。

原理:将系统中的所有内存块根据其存活时间划分为不同的集合,每一个集合就成为一个“代”,垃圾收集的频率随着“代”的存活时间的增大而减小。也就是说,活得越长的对象,就越不可能是垃圾,就应该减少对它的垃圾收集频率。那么如何来衡量这个存活时间:通常是利用几次垃圾收集动作来衡量,如果一个对象经过的垃圾收集次数越多,可以得出:该对象存活时间就越长。

举例说明:

当某些内存块M经过了3次垃圾收集的清洗之后还存活时,我们就将内存块M划到一个集合A中去,而新分配的内存都划分到集合B中去。当垃圾收集开始工作时,大多数情况都只对集合B进行垃圾回收,而对集合A进行垃圾回收要隔相当长一段时间后才进行,这就使得垃圾收集机制需要处理的内存少了,效率自然就提高了。在这个过程中,集合B中的某些内存块由于存活时间长而会被转移到集合A中,当然,集合A中实际上也存在一些垃圾,这些垃圾的回收会因为这种分代的机制而被延迟。
在Python中,总共有3“代”,也就是Python实际上维护了3条链表。具体可以查看Python源码详细了解。

Python 相关文章推荐
Python3基础之输入和输出实例分析
Aug 18 Python
Python文件右键找不到IDLE打开项解决办法
Jun 08 Python
深入浅析python定时杀进程
Jun 06 Python
Python装饰器用法实例总结
Feb 07 Python
对Python中9种生成新对象的方法总结
May 23 Python
Python中的random.uniform()函数教程与实例解析
Mar 02 Python
Python面向对象程序设计类变量与成员变量、类方法与成员方法用法分析
Apr 12 Python
浅析Python3中的对象垃圾收集机制
Jun 06 Python
django3.02模板中的超链接配置实例代码
Feb 04 Python
Python基于gevent实现高并发代码实例
May 15 Python
pyecharts调整图例与各板块的位置间距实例
May 16 Python
Python实现学生管理系统(面向对象版)
Jun 24 Python
python中将字典转换成其json字符串
Jul 16 #Python
记录Django开发心得
Jul 16 #Python
Python实现动态添加类的属性或成员函数的解决方法
Jul 16 #Python
Python重新引入被覆盖的自带function
Jul 16 #Python
Python实现扫描指定目录下的子目录及文件的方法
Jul 16 #Python
python re正则表达式模块(Regular Expression)
Jul 16 #Python
Web服务器框架 Tornado简介
Jul 16 #Python
You might like
php error_log 函数的使用
2009/04/13 PHP
fetchAll()与mysql_fetch_array()的区别详解
2013/06/05 PHP
PHP循环输出指定目录下的所有文件和文件夹路径例子(简单实用)
2014/05/10 PHP
正确的PHP匹配UTF-8中文的正则表达式
2015/05/13 PHP
[原创]PHP正则删除html代码中a标签并保留标签内容的方法
2017/05/23 PHP
PHP中类型转换 ,常量,系统常量,魔术常量的详解
2017/10/26 PHP
PHP双向链表定义与用法示例
2018/01/31 PHP
php 多继承的几种常见实现方法示例
2019/11/18 PHP
javascript 单例/单体模式(Singleton)
2011/04/07 Javascript
Javascript学习笔记之函数篇(五) : 构造函数
2014/11/23 Javascript
关于延迟加载JavaScript
2015/05/05 Javascript
js实现PC端和移动端刮卡效果
2020/03/27 Javascript
在Vue组件中使用 TypeScript的方法
2018/02/28 Javascript
解决layui-table单元格设置为百分比在ie8下不能自适应的问题
2019/09/28 Javascript
微信小程序事件流原理解析
2019/11/27 Javascript
Vue 使用typescript如何优雅的调用swagger API
2020/09/01 Javascript
Mac OS X10.9安装的Python2.7升级Python3.3步骤详解
2013/12/04 Python
python 中的int()函数怎么用
2017/10/17 Python
Python数据结构之图的应用示例
2018/05/11 Python
pytorch对可变长度序列的处理方法详解
2018/12/08 Python
Python中注释(多行注释和单行注释)的用法实例
2019/08/28 Python
Python编程快速上手——疯狂填词程序实现方法分析
2020/02/29 Python
python三引号如何输入
2020/07/06 Python
Selenium alert 弹窗处理的示例代码
2020/08/06 Python
Python3合并两个有序数组代码实例
2020/08/11 Python
快时尚眼镜品牌,全国连锁眼镜店:LOHO眼镜生活
2018/10/08 全球购物
德国W家官网,可直邮中国的母婴商城:Windeln.de
2021/03/03 全球购物
毕业实习个人鉴定范文
2013/12/10 职场文书
户外拓展活动方案
2014/02/11 职场文书
公司中秋节活动方案
2014/02/12 职场文书
园林技术个人的自我评价
2014/02/15 职场文书
大学生党员自我评价
2015/03/04 职场文书
幼儿园六一儿童节主持词
2015/06/30 职场文书
Jsonp劫持学习
2021/04/01 PHP
使用Pytorch训练two-head网络的操作
2021/05/28 Python
JS实现简单的九宫格抽奖
2022/06/28 Javascript