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 相关文章推荐
python调用shell的方法
Nov 20 Python
python网络编程实例简析
Sep 26 Python
Python编程之微信推送模板消息功能示例
Aug 21 Python
无法使用pip命令安装python第三方库的原因及解决方法
Jun 12 Python
python使用for循环计算0-100的整数的和方法
Feb 01 Python
python2和python3在处理字符串上的区别详解
May 29 Python
Python中Flask-RESTful编写API接口(小白入门)
Dec 11 Python
Python计算机视觉里的IOU计算实例
Jan 17 Python
Tensorflow不支持AVX2指令集的解决方法
Feb 03 Python
python tkinter之 复选、文本、下拉的实现
Mar 04 Python
pandas dataframe 中的explode函数用法详解
May 18 Python
只用40行Python代码就能写出pdf转word小工具
May 31 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
如何选购合适的收音机
2021/03/01 无线电
用PHP实现小型站点广告管理(修正版)
2006/10/09 PHP
php数组函数序列之sort() 对数组的元素值进行升序排序
2011/11/02 PHP
如何让thinkphp在模型中自动完成session赋值小教程
2014/09/05 PHP
Yii框架结合sphinx,Ajax实现搜索分页功能示例
2016/10/18 PHP
Yii框架数据模型的验证规则rules()被执行的方法
2016/12/02 PHP
PHP+MySQL使用mysql_num_rows实现模糊查询图书信息功能
2018/05/31 PHP
Laravel统计一段时间间隔的数据方法
2019/10/09 PHP
JavaScript日历实现代码
2010/09/12 Javascript
在javaScript中关于submit和button的区别介绍
2013/10/20 Javascript
jQuery对Select的操作大集合(收藏)
2013/12/28 Javascript
详解JavaScript中基于原型prototype的继承特性
2016/05/05 Javascript
JS在onclientclick里如何控制onclick的执行
2016/05/30 Javascript
Bootstrap标签页(Tab)插件使用方法
2017/03/21 Javascript
基于JavaScript+HTML5 实现打地鼠小游戏逻辑流程图文详解(附完整代码)
2017/11/02 Javascript
jQuery.extend 与 jQuery.fn.extend的用法及区别实例分析
2018/07/25 jQuery
JavaScript设计模式之享元模式实例详解
2019/01/17 Javascript
JavaScript定时器设置、使用与倒计时案例详解
2019/07/08 Javascript
纯 JS 实现放大缩小拖拽功能(完整代码)
2019/11/25 Javascript
django模型中的字段和model名显示为中文小技巧分享
2014/11/18 Python
Python中文字符串截取问题
2015/06/15 Python
python正则表达式的使用
2017/06/12 Python
python爬虫增加访问量的方法
2019/08/22 Python
python实现猜数字游戏
2020/03/25 Python
解决tensorboard多个events文件显示紊乱的问题
2020/02/15 Python
python之MSE、MAE、RMSE的使用
2020/02/24 Python
Django实现将views.py中的数据传递到前端html页面,并展示
2020/03/16 Python
基于Python爬取素材网站音频文件
2020/10/21 Python
浅谈h5自定义audio(问题及解决)
2016/08/19 HTML / CSS
Schecker荷兰:狗狗用品和配件
2019/06/06 全球购物
毕业设计计划书
2014/01/09 职场文书
2014年打非治违工作总结
2014/11/13 职场文书
2014年材料员工作总结
2014/11/19 职场文书
网吧管理制度范本
2015/08/05 职场文书
MySql学习笔记之事务隔离级别详解
2021/05/12 MySQL
Winsows11性能如何? win11性能测评多核竟比Win10差了10%
2021/11/21 数码科技