简介: # Python GC 机制 对于 Python 这种高级语言来说,开发者不须要本人治理和保护内存。Python 采纳了援用计数机制为主,标记 - 革除和分代收集两种机制为辅的垃圾回收机制。首先,须要搞清楚变量和对象的关系:* 变量:通过变量指针援用对象。变量指针指向具体对象的内存空间,取对象的值。* 对象,类型已知,每个对象都蕴含一个头部信息(头部信息:类型标识符和援用计数器)
Python GC 机制
对于 Python 这种高级语言来说,开发者不须要本人治理和保护内存。Python 采纳了援用计数机制为主,标记 - 革除和分代收集两种机制为辅的垃圾回收机制。
首先,须要搞清楚变量和对象的关系:
- 变量:通过变量指针援用对象。变量指针指向具体对象的内存空间,取对象的值。
- 对象,类型已知,每个对象都蕴含一个头部信息(头部信息:类型标识符和援用计数器)
援用计数
python 里每一个货色都是对象,它们的外围就是一个构造体:PyObject,其中 ob_refcnt 就是援用计数。当一个对象有新的援用时,ob_refcnt 就会减少,当援用它的对象被删除,ob_refcnt 就会缩小。当援用计数为 0 时,该对象生命就完结了。
typedef struct_object {
int ob_refcnt;
struct_typeobject *ob_type;
} PyObject;
#define Py_INCREF(op) ((op)->ob_refcnt++) // 减少计数
#define Py_DECREF(op) \ // 缩小计数
if (--(op)->ob_refcnt != 0) \
; \
else \
__Py_Dealloc((PyObject *)(op))
能够应用 sys.getrefcount()函数获取对象的援用计数,须要留神的是,应用时会比预期的援用次数多 1,起因是调用时会针对于查问的对象主动产生一个长期援用。
上面简略展示一下援用计数的变动过程。
- 一开始创立 3 个对象,援用计数别离是 1。
- 之后将 n1 指向了新的对象 ”JKL”,则之前的对象“ABC”的援用计数就变成 0 了。这时候,Python 的垃圾回收器开始工作,将“ABC”开释。
- 接着,让 n2 援用 n1。“DEF”不再被援用,“JKL”因为被 n1、n2 同时援用,所以援用计数变成了 2。
>>> n1 = "ABC"
>>> n2 = "DEF"
>>> n3 = "GHI"
>>> sys.getrefcount(n1)
2
>>> sys.getrefcount(n2)
2
>>> sys.getrefcount(n3)
2
>>> n1 = "JKL"
>>> sys.getrefcount(n1)
2
>>> n2 = n1
>>> sys.getrefcount(n1)
3
>>> sys.getrefcount(n2)
3
>>> sys.getrefcount(n3)
2
优缺点:
长处:实时性好。一旦没有援用,内存就间接开释了。实时性还带来一个益处:解决回收内存的工夫摊派到了平时。
毛病:保护援用计数耗费资源;循环援用无奈解决。
如下图,典型的循环援用场景。对象除了被变量援用 n1、n2 外,还被对方的 prev 或 next 指针援用,造成了援用计数为 2。之后 n1、n2 设成 null 之后,援用计数依然为 1,导致对象无奈被回收。
标记 - 革除、分代收集
Python 采纳标记 - 革除策略来解决循环援用的问题。然而该机制会导致应用程序卡住,为了缩小程序暂停的工夫,又通过 “分代回收”(Generational Collection) 以空间换工夫的办法进步垃圾回收效率。详见 Python 垃圾回收机制!十分实用
Python C 扩大的援用计数
Python 提供了 GC 机制,保障对象不被应用的时候会被开释掉,开发者不须要过多关怀内存治理的问题。然而当应用 C 扩大的时候,就不这么简略了,必须须要了解 CPython 的援用计数。
当应用 C 扩大应用 Python 时,援用计数会随着 PyObjects 的创立主动加 1,然而当开释该 PyObjects 的时候,咱们须要显示的将 PyObjects 的援用计数减 1,否则会呈现内存透露。
#include "Python.h"
void print_hello_world(void) {
PyObject *pObj = NULL;
pObj = PyBytes_FromString("Hello world\n"); /* Object creation, ref count = 1. */
PyObject_Print(pLast, stdout, 0);
Py_DECREF(pObj); /* ref count becomes 0, object deallocated.
* Miss this step and you have a memory leak. */
}
有亮点尤其须要留神:
- PyObjects 援用计数为 0 后,不能再拜访。相似于 C 语言 free 后,不能再拜访对象。
- Py_INCREF、Py_DECREF 必须成对呈现。相似于 C 语言 malloc、free 的关系。
Python 有三种援用模式,别离为“New”,“Stolen”和“Borrowed”援用。
New 援用
通过 Python C Api 创立出的 PyObject,调用者对该 PyObject 具备齐全的所有权。个别 Python 文档这样体现:
PyObject* PyList_New(int len)
Return value: New reference.
Returns a new list of length len on success, or NULL on failure.
针对于 New 援用的 PyObject,有如下两种抉择。否则,就会呈现内存透露。
- 应用实现后,调用 Py_DECREF 将其开释掉。
void MyCode(arguments) {
PyObject *pyo;
...
pyo = Py_Something(args);
...
Py_DECREF(pyo);
}
- 将援用通过函数返回值等模式传递给下层调用函数,然而接收者必须负责最终的 Py_DECREF 调用。
void MyCode(arguments) {
PyObject *pyo;
...
pyo = Py_Something(args);
...
return pyo;
}
应用样例:
static PyObject *subtract_long(long a, long b) {
PyObject *pA, *pB, *r;
pA = PyLong_FromLong(a); /* pA: New reference. */
pB = PyLong_FromLong(b); /* pB: New reference. */
r = PyNumber_Subtract(pA, pB); /* r: New reference. */
Py_DECREF(pA); /* My responsibility to decref. */
Py_DECREF(pB); /* My responsibility to decref. */
return r; /* Callers responsibility to decref. */
}
// 谬误的例子,a、b 两个 PyObject 透露。r = PyNumber_Subtract(PyLong_FromLong(a), PyLong_FromLong(b));
Stolen 援用
当创立的 PyObject 传递给其余的容器,例如 PyTuple_SetItem、PyList_SetItem。
static PyObject *make_tuple(void) {
PyObject *r;
PyObject *v;
r = PyTuple_New(3); /* New reference. */
v = PyLong_FromLong(1L); /* New reference. */
/* PyTuple_SetItem "steals" the new reference v. */
PyTuple_SetItem(r, 0, v);
/* This is fine. */
v = PyLong_FromLong(2L);
PyTuple_SetItem(r, 1, v);
/* More common pattern. */
PyTuple_SetItem(r, 2, PyUnicode_FromString("three"));
return r; /* Callers responsibility to decref. */
}
然而,须要留神 PyDict_SetItem 外部会援用计数加一。
Borrowed 援用
Python 文档中,Borrowed 援用的体现:
PyObject* PyTuple_GetItem(PyObject *p, Py_ssize_t pos)
Return value: Borrowed reference.
Borrowed 援用的所有者不应该调用 Py_DECREF(),应用 Borrowed 援用在函数退出时不会呈现内存泄露。。然而不要让一个对象解决未爱护的状态 Borrowed 援用,如果对象解决未爱护状态,它随时可能会被销毁。
例如:从一个 list 获取对象,持续操作它,但并不递增它的援用。PyList_GetItem 会返回一个 borrowed reference,所以 item 处于未爱护状态。一些其余的操作可能会从 list 中将这个对象删除(递加它的援用计数,或者开释它),导致 item 成为一个悬垂指针。
bug(PyObject *list) {PyObject *item = PyList_GetItem(list, 0);
PyList_SetItem(list, 1, PyInt_FromLong(0L));
PyObject_Print(item, stdout, 0); /* BUG! */
}
no_bug(PyObject *list) {PyObject *item = PyList_GetItem(list, 0);
Py_INCREF(item); /* Protect item. */
PyList_SetItem(list, 1, PyInt_FromLong(0L));
PyObject_Print(item, stdout, 0);
Py_DECREF(item);
}
原文链接
本文为阿里云原创内容,未经容许不得转载。