简介: # 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);}
原文链接
本文为阿里云原创内容,未经容许不得转载。