kimber_kimber

导航

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编辑  收藏  举报