内存管理
Python 提供了自动化内存管理,利用解释器进行内存空间管理,极大减轻程序员负担。Cpython 使用三个关键字:
引用计数
当引用计数器 ob_refcnt 为 0 时,释放对象。
// Python 每一个对象都是 PyObject
typedef struct _object {
_PyObject_HEAD_EXTRA
// 引用计数器,反映有多少变量引用此对象
Py_ssize_t ob_refcnt;
struct _typeobject *ob_type;
} PyObject;
以下情况会导致引用计数加 1 :
-
对象被创建
-
对象被引用
-
对象作为参数传入到一个函数中
-
对象作为元素存储到一个容器中
以下情况会导致引用计数减1:
-
用
del
语句显示删除对象引用 -
对象引用被重新赋值其他对象
-
一个对象离开它所在的作用域
-
持有该对象的容器自身被销毁
-
持有该对象的容器删除该对象
遇到循环引用时,计数器会不停增加,需要其它垃圾回收算法!
标记清理
主要分为标记阶段和清除阶段:
-
标记阶段:遍历所有对象,若对象被其他对象引用,就标记该对象为可达。
-
清除阶段:遍历所有对象,如果发现某个对象没有标记为可达,就将其回收。
具体实现过程
使用双端链表 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 修改