简介: # 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);}

原文链接
本文为阿里云原创内容,未经容许不得转载。