Python垃圾回收算法

内存管理

Python 提供了自动化内存管理,利用解释器进行内存空间管理,极大减轻程序员负担。Cpython 使用三个关键字:

引用计数

Untitled

当引用计数器 ob_refcnt 为 0 时,释放对象。


// Python 每一个对象都是 PyObject

typedef struct _object {

    _PyObject_HEAD_EXTRA

      // 引用计数器,反映有多少变量引用此对象

    Py_ssize_t ob_refcnt; 

    struct _typeobject *ob_type;

} PyObject;

以下情况会导致引用计数加 1 :

  • 对象被创建

  • 对象被引用

  • 对象作为参数传入到一个函数中

  • 对象作为元素存储到一个容器中

以下情况会导致引用计数减1:

  • del语句显示删除对象引用

  • 对象引用被重新赋值其他对象

  • 一个对象离开它所在的作用域

  • 持有该对象的容器自身被销毁

  • 持有该对象的容器删除该对象

遇到循环引用时,计数器会不停增加,需要其它垃圾回收算法!

标记清理

主要分为标记阶段和清除阶段:

  • 标记阶段:遍历所有对象,若对象被其他对象引用,就标记该对象为可达。

  • 清除阶段:遍历所有对象,如果发现某个对象没有标记为可达,就将其回收。

Untitled 1

具体实现过程

使用双端链表 A 存储被扫描的容器对象,使用双端链表 B 存储不可达对象:

  • ref_count:引用计数

  • gc_ref:ref_count 的副本

设初始链表如下:

遍历链表 A ,执行 gc_ref - 1 ,用于解除循环引用对引用计数的影响。

再次遍历链表 A ,按顺序执行以下两步:

1.若 gc_ref == 0 ,标记该对象为“暂时不可达”(GC_TENTATIVELY_UNREACHABLE),并被移动到链表 B 中。

2.若 gc_ref != 0 ,标记该对象为“可达”(GC_REACHABLE),并递归的将该节点可到达的节点标记为“可达”,链表 B 中被标记为“可达”的节点重新放回到链表 A 。

释放链表 B 中的节点。

分代回收

分代回收基本思想:对象存在的时间越长,是垃圾的可能性就越小,应该尽量不对这样的对象进行垃圾回收

  • 新生对象放入第 0 代

  • 如果对象在垃圾回收扫描中存活,放入第 1 代

  • 如果对象在垃圾回收扫描中存活,放入第 2 代

代数越多,垃圾收集频率越少。

分代回收扫描的门限值可通过 gc 模块的 get_threshold 查看,通过 set_threshold 修改