python--垃圾回收机制
python 垃圾回收机制有三种:引用计数,标记-清除,分代收集
引用计数为主要策略,标记-清除和分代收集为辅助策略
1.引用计数
在学习 Python 的整个过程中,我们一直在强调,Python 中一切皆对象,也就是说,在 Python 中你用到的一切变量,本质上都是类对象。
那么,如何知道一个对象永远都不能再使用了呢?很简单,就是当这个对象的引用计数值为 0 时,说明这个对象永不再用,自然它就变成了垃圾,需要被回收。
在Python中每一个对象的核心就是一个结构体PyObject,它的内部有一个引用计数器(ob_refcnt)。程序在运行的过程中会实时的更新ob_refcnt的值,来反映引用当前对象的名称数量。
typedef struct_object { int ob_refcnt; struct_typeobject *ob_type; } PyObject;
导致引用计数 +1的情况:
- 对象被创建,如 a=1
- 对象被引用,如b=a
- 对象被作为参数传递到一个函数中,如func(a)
- 对象作为一个元素,存储在一个容器中,如 list[a]
导致引用计数-1 的情况 :
- 对象别名被显示销毁 ,如 del a
- 对象别名被赋予新的对象,如 a=2
- 一个对象,离开它的作用域,如 函数fun() 执行完毕,函数内的局部变量被释放
- 对象所在的容器被销毁,或从容器中删除对象
当引用计数器为0 时内存会自动释放掉,非常方便 (当然也可以del + gc.colloct()手动释放),但是引用计数也有缺点
引用计数的缺点:
- 逻辑简单,实现麻烦,每个对象需要分配单独的空间来统计引用计数,这无形中加大的空间的负担,并且需要对引用计数进行维护,在维护的时候很容易会出错
- 当需要释放一个大的对象时,比如字典,需要对引用的所有对象循环嵌套调用,从而可能会花费比较长的时间
- 无法解决循环引用的问题
2.标记-清除 解决循环引用问题
首先,只有容器对象才会产生循环引用的问题,而像数字,字符串这类简单类型不会出现循环引用。作为一种优化策略,对于只包含简单类型的元组也不在标记清除算法的考虑之列)
下面是一个循环引用的例子
1 def func(): 2 a = [i for i in range(10000000)] 3 b = [i for i in range(10000000)] 4 a.append(b) 5 b.append(a)
标记-清除的思想为:
第一步:标记--遍历所有的对象,如果是可达的(reachable),也就是还有对象引用它,那么就标记该对象为可达;
第二步:清除--再次遍历对象,如果发现某个对象没有标记为可达,则就将其回收
3.分代回收 解决循环引用问题
分代回收是基于标记-清除之上的方法,是一种以时间换空间的方法
在循环引用对象的回收中,整个应用程序会被暂停,为了减少应用程序暂停的时间,Python 通过“分代回收”(Generational Collection)以空间换时间的方法提高垃圾回收效率。
分代回收是基于这样的一个统计事实,对于程序,存在一定比例的内存块的生存周期比较短;而剩下的内存块,生存周期会比较长,甚至会从程序开始一直持续到程序结束。生存期较短对象的比例通常在 80%~90% 之间,这种思想简单点说就是:对象存在时间越长,越可能不是垃圾,应该越少去收集。这样在执行标记-清除算法时可以有效减小遍历的对象数,从而提高垃圾回收的速度。
我们把这些内存分为 年轻代,中年代,老年代,分别对应生命周期从短到长的内存,他们对应的是3个链表,它们的垃圾收集频率与对象的存活时间的增大而减小。新创建的对象都会分配在年轻代,年轻代链表的总数达到上限时,Python垃圾收集机制就会被触发,把那些可以被回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,依此类推,老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内
posted on 2021-03-05 18:22 kimber_kimber 阅读(16) 评论(0) 编辑 收藏 举报