概述
无论何种垃圾收集机制, 一般都是两阶段: 垃圾检测和垃圾回收.
在Python中, 大多数对象的生命周期都是通过对象的引用计数来管理的.
问题: 但是存在循环引用的问题: a 引用 b, b 引用 a, 导致每一个对象的引用计数都不为0, 所占用的内存永远不会被回收
要解决循环引用: 必需引入其他垃圾收集技术来打破循环引用. Python中使用了标记-清除
以及分代收集
即, Python 中垃圾回收机制: 引用计数(主要), 标记清除, 分代收集(辅助)
引用计数
引用计数, 意味着必须在每次分配和释放内存的时候, 加入管理引用计数的动作
引用计数的优点: 最直观最简单, 实时性, 任何内存, 一旦没有指向它的引用, 就会立即被回收
计数存储
回顾 Python 的对象


e.g. 引用计数增加以及减少
1 2 3 4 5 6 7 8 9 10 11 |
>>> from sys import getrefcount >>> >>> a = [1, 2, 3] >>> getrefcount(a) 2 >>> b = a >>> getrefcount(a) 3 >>> del b >>> getrefcount(a) 2 |
计数增加
增加对象引用计数, refcnt incr
1 2 3 |
#define Py_INCREF(op) ( _Py_INC_REFTOTAL _Py_REF_DEBUG_COMMA ((PyObject*)(op))->ob_refcnt++) |
计数减少
减少对象引用计数, refcnt desc
1 2 3 4 5 6 7 8 9 10 11 |
#define _Py_DEC_REFTOTAL _Py_RefTotal-- #define _Py_REF_DEBUG_COMMA , #define Py_DECREF(op) do { if (_Py_DEC_REFTOTAL _Py_REF_DEBUG_COMMA --((PyObject*)(op))->ob_refcnt != 0) _Py_CHECK_REFCNT(op) else _Py_Dealloc((PyObject *)(op)); } while (0) |
即, 发现refcnt变成0的时候, 会调用_Py_Dealloc
1 2 3 4 5 6 7 |
PyAPI_FUNC(void) _Py_Dealloc(PyObject *); #define _Py_REF_DEBUG_COMMA , #define _Py_Dealloc(op) ( _Py_INC_TPFREES(op) _Py_COUNT_ALLOCS_COMMA (*Py_TYPE(op)->tp_dealloc)((PyObject *)(op))) #endif /* !Py_TRACE_REFS */ |
会调用各自类型的tp_dealloc
例如dict
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
PyTypeObject PyDict_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "dict", sizeof(PyDictObject), 0, (destructor)dict_dealloc, /* tp_dealloc */ .... } static void dict_dealloc(register PyDictObject *mp) { ..... // 如果满足条件, 放入到缓冲池freelist中 if (numfree tp_free((PyObject *)mp); Py_TRASHCAN_SAFE_END(mp) } |
Python基本类型的tp_dealloc
, 通常都会与各自的缓冲池机制相关, 释放会优先放入缓冲池中(对应的分配会优先从缓冲池取). 这个内存分配与回收同缓冲池机制相关
当无法放入缓冲池时, 会调用各自类型的tp_free
int, 比较特殊
1 2 |
// int, 通用整数对象缓冲池机制 (freefunc)int_free, /* tp_free */ |
string
1 2 |
// string PyObject_Del, /* tp_free */ |
dict/tuple/list
1 |
PyObject_GC_Del, /* tp_free */ |
然后, 我们再回头看, 自定义对象的tp_free
1 2 3 4 5 6 |
PyTypeObject PyType_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "type", /* tp_name */ ... PyObject_GC_Del, /* tp_free */ }; |
即, 最终, 当计数变为0, 触发内存回收动作. 涉及函数PyObject_Del
和PyObject_GC_Del
, 并且, 自定义类以及容器类型(dict/list/tuple/set等)使用的都是后者PyObject_GC_Del
.
内存回收 PyObject_Del / PyObject_GC_Del
如果引用计数=0:
1 2 |
1. 放入缓冲池 2. 真正销毁, PyObject_Del/PyObject_GC_Del内存操作 |
这两个操作都是进行内存级别的操作
- PyObject_Del
PyObject_Del(op) releases the memory allocated for an object. It does not
run a destructor — it only frees the memory. PyObject_Free is identical.
这块删除, PyObject_Free
涉及到了Python底层内存的分配和管理机制, 具体见前面的博文
- PyObject_GC_Del
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
void PyObject_GC_Del(void *op) { PyGC_Head *g = AS_GC(op); // Returns true if a given object is tracked if (IS_TRACKED(op)) // 从跟踪链表中移除 gc_list_remove(g); if (generations[0].count > 0) { generations[0].count--; } PyObject_FREE(g); } |
IS_TRACKED
涉及到标记-清除的机制
generations
涉及到了分代回收
PyObject_FREE
, 则和Python底层内存池机制相关
标记-清除
问题: 什么对象可能产生循环引用?
只需要关注关注可能产生循环引用的对象
PyIntObject/PyStringObject等不可能
Python中的循环引用总是发生在container对象之间, 所谓containser对象即是内部可持有对其他对象的引用: list/dict/class/instance等等
垃圾收集带来的开销依赖于container对象的数量, 必需跟踪所创建的每一个container对象, 并将这些对象组织到一个集合中.
可收集对象链表
可收集对象链表: 将需要被收集和跟踪的container, 放到可收集的链表中
任何一个python对象都分为两部分: PyObject_HEAD + 对象本身数据
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/* PyObject_HEAD defines the initial segment of every PyObject. */ #define PyObject_HEAD _PyObject_HEAD_EXTRA Py_ssize_t ob_refcnt; struct _typeobject *ob_type; //---------------------------------------------------- #define _PyObject_HEAD_EXTRA struct _object *_ob_next; struct _object *_ob_prev; // 双向链表结构, 垃圾回收 |
可收集对象链表
1 2 3 4 5 6 7 |