经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » Python » 查看文章
Python垃圾回收
来源:cnblogs  作者:nisonGe  时间:2023/9/9 10:42:02  对本文有异议

Python版本

v3.9.17

分析代码的过程比较枯燥,可以直接跳转到总结。

只能被其他对象引用类型

比如:longobject、floatobject

floatobject

以floatobject为例子来分析,先看看结构定义

  1. typedef struct {
  2. PyObject_HEAD
  3. double ob_fval;
  4. } PyFloatObject;
  5. // 展开PyObject_HEAD后
  6. typedef struct {
  7. PyObject ob_base;
  8. double ob_fval;
  9. } PyFloatObject;
  10. typedef struct _object {
  11. _PyObject_HEAD_EXTRA
  12. Py_ssize_t ob_refcnt;
  13. PyTypeObject *ob_type;
  14. } PyObject;

在PyObject中的_PyObject_HEAD_EXTRA,只有在编译时指定--with-trace-refs才有效,这里忽略即可。

  1. ./configure --with-trace-refs

可以看到在PyObject里有一个ob_refcnt的属性,这个就是引用计数。
当对引用计数减为0时,就会调用各类型对应的析构函数。

  1. define Py_DECREF(op) _Py_DECREF(_PyObject_CAST(op))
  2. void _Py_Dealloc(PyObject *op)
  3. {
  4. destructor dealloc = Py_TYPE(op)->tp_dealloc;
  5. (*dealloc)(op);
  6. }
  7. static inline void _Py_DECREF(PyObject *op)
  8. {
  9. if (--op->ob_refcnt != 0) {
  10. }
  11. else {
  12. _Py_Dealloc(op);
  13. }
  14. }

能引用其他对象的类型

比如listobject,dictobject...

listobject

以listobject为例子来分析,先看看结构定义

  1. typedef struct {
  2. PyObject_VAR_HEAD
  3. PyObject **ob_item;
  4. Py_ssize_t allocated;
  5. } PyListObject;
  6. // 展开 PyObject_VAR_HEAD
  7. typedef struct {
  8. PyVarObject ob_base;
  9. PyObject **ob_item;
  10. Py_ssize_t allocated;
  11. } PyListObject;
  12. typedef struct {
  13. PyObject ob_base;
  14. Py_ssize_t ob_size; /* Number of items in variable part */
  15. } PyVarObject;

可以看出,PyObject_VAR_HEAD也就比PyObject_HEAD多了一个Py_ssize_t ob_size而已,这个属性是用来表示这个可变对象里元素数量。

因为可以引用其他对象,就有可能会出现环引用问题,这种问题如果再使用引用计数来作为GC就会出现问题。

  1. lst1 = []
  2. lst2 = []
  3. lst1.append(lst2)
  4. lst2.append(lst1)

当然这种情况可以使用弱引用,或者手动解除环引用。这些解决方案这里不深入,现在主要看看python是怎样应对这种情况。

对于这类型的对象在申请内存的时候调用的是PyObject_GC_New,而不可变类型是用PyObject_MALLOC。为了减少篇幅,删掉了一些判断逻辑。

  1. typedef struct {
  2. // Pointer to next object in the list.
  3. // 0 means the object is not tracked
  4. uintptr_t _gc_next;
  5. // Pointer to previous object in the list.
  6. // Lowest two bits are used for flags documented later.
  7. uintptr_t _gc_prev;
  8. } PyGC_Head;
  9. #define FROM_GC(g) ((PyObject *)(((PyGC_Head *)g)+1))
  10. static PyObject * _PyObject_GC_Alloc(int use_calloc, size_t basicsize)
  11. {
  12. PyThreadState *tstate = _PyThreadState_GET();
  13. GCState *gcstate = &tstate->interp->gc;
  14. size_t size = sizeof(PyGC_Head) + basicsize;
  15. PyGC_Head *g;
  16. g = (PyGC_Head *)PyObject_Malloc(size);
  17. g->_gc_next = 0;
  18. g->_gc_prev = 0;
  19. gcstate->generations[0].count++; /* number of allocated GC objects */
  20. if (/* 判断是否可以执行GC */)
  21. {
  22. gcstate->collecting = 1;
  23. collect_generations(tstate);
  24. gcstate->collecting = 0;
  25. }
  26. PyObject *op = FROM_GC(g);
  27. return op;
  28. }

在可变对象中,python又加上了一个PyGC_Head。通过这个PyGC_Head将listobject链接到gc列表中。

在分配完listobject内存后,紧接着调用_PyObject_GC_TRACK,链接到gc列表中。

  1. static inline void _PyObject_GC_TRACK_impl(const char *filename, int lineno,
  2. PyObject *op)
  3. {
  4. PyGC_Head *gc = _Py_AS_GC(op);
  5. PyThreadState *tstate = _PyThreadState_GET();
  6. PyGC_Head *generation0 = tstate->interp->gc.generation0;
  7. PyGC_Head *last = (PyGC_Head*)(generation0->_gc_prev);
  8. _PyGCHead_SET_NEXT(last, gc);
  9. _PyGCHead_SET_PREV(gc, last);
  10. _PyGCHead_SET_NEXT(gc, generation0);
  11. generation0->_gc_prev = (uintptr_t)gc;
  12. }

通过这里的变量名,可以猜测使用到了分代垃圾回收。

分代回收

python手动执行垃圾回收一般调用gc.collect(generation=2)函数。

  1. #define NUM_GENERATIONS 3
  2. #define GC_COLLECT_METHODDEF {"collect", (PyCFunction)(void(*)(void))gc_collect, METH_FASTCALL|METH_KEYWORDS, gc_collect__doc__},
  3. static PyObject *
  4. gc_collect(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
  5. {
  6. PyObject *return_value = NULL;
  7. int generation = NUM_GENERATIONS - 1;
  8. Py_ssize_t _return_value;
  9. _return_value = gc_collect_impl(module, generation);
  10. if ((_return_value == -1) && PyErr_Occurred()) {
  11. goto exit;
  12. }
  13. return_value = PyLong_FromSsize_t(_return_value);
  14. exit:
  15. return return_value;
  16. }

具体执行在gc_collect_impl函数中,接着往下

  1. static Py_ssize_t gc_collect_impl(PyObject *module, int generation)
  2. {
  3. PyThreadState *tstate = _PyThreadState_GET();
  4. GCState *gcstate = &tstate->interp->gc;
  5. Py_ssize_t n;
  6. if (gcstate->collecting) {
  7. /* already collecting, don't do anything */
  8. n = 0;
  9. }
  10. else {
  11. gcstate->collecting = 1;
  12. n = collect_with_callback(tstate, generation);
  13. gcstate->collecting = 0;
  14. }
  15. return n;
  16. }

可以看到,如果已经在执行GC,则直接返回。接着看collect_with_callback

  1. static Py_ssize_t
  2. collect_with_callback(PyThreadState *tstate, int generation)
  3. {
  4. assert(!_PyErr_Occurred(tstate));
  5. Py_ssize_t result, collected, uncollectable;
  6. invoke_gc_callback(tstate, "start", generation, 0, 0);
  7. result = collect(tstate, generation, &collected, &uncollectable, 0);
  8. invoke_gc_callback(tstate, "stop", generation, collected, uncollectable);
  9. assert(!_PyErr_Occurred(tstate));
  10. return result;
  11. }

其中invoke_gc_callback是调用通过gc.callbacks注册的回调函数,这里我们忽略,重点分析collect函数。

collect函数签名
这段代码很长,我们拆分开来分析,这里会去除掉一些DEBUG相关的逻辑。

  1. static Py_ssize_t collect(PyThreadState *tstate, int generation,Py_ssize_t *n_collected, Py_ssize_t *n_uncollectable, int nofail);
  1. 将新生代的对象合并到指定代的对象列表中。
  1. /* merge younger generations with one we are currently collecting */
  2. for (i = 0; i < generation; i++) {
  3. gc_list_merge(GEN_HEAD(gcstate, i), GEN_HEAD(gcstate, generation));
  4. }

比如调用gc.collect(2),就表示启动全部的垃圾回收。这里就会将第0、1代的对象合并到第2代上。合并之后第0、1代上就空了,全部可GC的对象都在第2代上。

  1. 推断不可达对象
  1. /* handy references */
  2. young = GEN_HEAD(gcstate, generation);
  3. if (generation < NUM_GENERATIONS-1)
  4. old = GEN_HEAD(gcstate, generation+1);
  5. else
  6. old = young;
  7. validate_list(old, collecting_clear_unreachable_clear);
  8. deduce_unreachable(young, &unreachable);

这里的young指针指向第2代的链表头,validate_list做校验,这里忽略,重点在deduce_unreachable函数中。

  1. static inline void
  2. deduce_unreachable(PyGC_Head *base, PyGC_Head *unreachable) {
  3. validate_list(base, collecting_clear_unreachable_clear);
  4. update_refs(base); // gc_prev is used for gc_refs
  5. subtract_refs(base);
  6. gc_list_init(unreachable);
  7. move_unreachable(base, unreachable); // gc_prev is pointer again
  8. validate_list(base, collecting_clear_unreachable_clear);
  9. validate_list(unreachable, collecting_set_unreachable_set);
  10. }

首先调用update_refs更新引用计数

  1. static inline void
  2. gc_reset_refs(PyGC_Head *g, Py_ssize_t refs)
  3. {
  4. g->_gc_prev = (g->_gc_prev & _PyGC_PREV_MASK_FINALIZED)
  5. | PREV_MASK_COLLECTING
  6. | ((uintptr_t)(refs) << _PyGC_PREV_SHIFT);
  7. }
  8. static void
  9. update_refs(PyGC_Head *containers)
  10. {
  11. PyGC_Head *gc = GC_NEXT(containers);
  12. for (; gc != containers; gc = GC_NEXT(gc)) {
  13. gc_reset_refs(gc, Py_REFCNT(FROM_GC(gc)));
  14. _PyObject_ASSERT(FROM_GC(gc), gc_get_refs(gc) != 0);
  15. }
  16. }

这里的逻辑就是遍历所有对象,然后赋值_gc_prev,设置为收集中的标识PREV_MASK_COLLECTING,然后将引用计数赋值给_gc_prev 。最后_gc_prev的内容如下。

更新完_gc_prev后,就开始调用subtrace_refs,遍历对象中的元素,判断元素是否也是可GC对象并且有收集中标记,如果是则减去该对象的计数。注意这里减去的是_gc_prev中的计数,而不是真正的计数ob_refcnt

  1. static int
  2. visit_decref(PyObject *op, void *parent)
  3. {
  4. _PyObject_ASSERT(_PyObject_CAST(parent), !_PyObject_IsFreed(op));
  5. if (_PyObject_IS_GC(op)) {
  6. PyGC_Head *gc = AS_GC(op);
  7. /* We're only interested in gc_refs for objects in the
  8. * generation being collected, which can be recognized
  9. * because only they have positive gc_refs.
  10. */
  11. if (gc_is_collecting(gc)) {
  12. gc_decref(gc);
  13. }
  14. }
  15. return 0;
  16. }
  17. static void
  18. subtract_refs(PyGC_Head *containers)
  19. {
  20. traverseproc traverse;
  21. PyGC_Head *gc = GC_NEXT(containers);
  22. for (; gc != containers; gc = GC_NEXT(gc)) {
  23. PyObject *op = FROM_GC(gc);
  24. traverse = Py_TYPE(op)->tp_traverse;
  25. (void) traverse(FROM_GC(gc),
  26. (visitproc)visit_decref,
  27. op);
  28. }
  29. }

更新计数值之后,就开始收集不可达对象,将对象移入到不可达列表中。unreachable

  1. /* A traversal callback for move_unreachable. */
  2. static int
  3. visit_reachable(PyObject *op, PyGC_Head *reachable)
  4. {
  5. if (!_PyObject_IS_GC(op)) {
  6. return 0;
  7. }
  8. PyGC_Head *gc = AS_GC(op);
  9. const Py_ssize_t gc_refs = gc_get_refs(gc);
  10. if (! gc_is_collecting(gc)) {
  11. return 0;
  12. }
  13. assert(gc->_gc_next != 0);
  14. if (gc->_gc_next & NEXT_MASK_UNREACHABLE) {
  15. PyGC_Head *prev = GC_PREV(gc);
  16. PyGC_Head *next = (PyGC_Head*)(gc->_gc_next & ~NEXT_MASK_UNREACHABLE);
  17. _PyObject_ASSERT(FROM_GC(prev),
  18. prev->_gc_next & NEXT_MASK_UNREACHABLE);
  19. _PyObject_ASSERT(FROM_GC(next),
  20. next->_gc_next & NEXT_MASK_UNREACHABLE);
  21. prev->_gc_next = gc->_gc_next; // copy NEXT_MASK_UNREACHABLE
  22. _PyGCHead_SET_PREV(next, prev);
  23. gc_list_append(gc, reachable);
  24. gc_set_refs(gc, 1);
  25. }
  26. else if (gc_refs == 0) {
  27. gc_set_refs(gc, 1);
  28. }
  29. else {
  30. _PyObject_ASSERT_WITH_MSG(op, gc_refs > 0, "refcount is too small");
  31. }
  32. return 0;
  33. }
  34. static void
  35. move_unreachable(PyGC_Head *young, PyGC_Head *unreachable)
  36. {
  37. PyGC_Head *prev = young;
  38. PyGC_Head *gc = GC_NEXT(young);
  39. while (gc != young) {
  40. if (gc_get_refs(gc)) {
  41. PyObject *op = FROM_GC(gc);
  42. traverseproc traverse = Py_TYPE(op)->tp_traverse;
  43. _PyObject_ASSERT_WITH_MSG(op, gc_get_refs(gc) > 0,
  44. "refcount is too small");
  45. (void) traverse(op,
  46. (visitproc)visit_reachable,
  47. (void *)young);
  48. _PyGCHead_SET_PREV(gc, prev);
  49. gc_clear_collecting(gc);
  50. prev = gc;
  51. }
  52. else {
  53. prev->_gc_next = gc->_gc_next;
  54. PyGC_Head *last = GC_PREV(unreachable);
  55. last->_gc_next = (NEXT_MASK_UNREACHABLE | (uintptr_t)gc);
  56. _PyGCHead_SET_PREV(gc, last);
  57. gc->_gc_next = (NEXT_MASK_UNREACHABLE | (uintptr_t)unreachable);
  58. unreachable->_gc_prev = (uintptr_t)gc;
  59. }
  60. gc = (PyGC_Head*)prev->_gc_next;
  61. }
  62. // young->_gc_prev must be last element remained in the list.
  63. young->_gc_prev = (uintptr_t)prev;
  64. // don't let the pollution of the list head's next pointer leak
  65. unreachable->_gc_next &= ~NEXT_MASK_UNREACHABLE;
  66. }

这段代码的逻辑是,遍历收集代中的所有对象,判断对象的计数值是否为0
如果等于0,则从收集代中移除,加入不可达列表中,然后打上不可达标记。
如果不等于0,则遍历对象的所有元素,如果元素已经被打上不可达标记,则把该元素从不可达列表中移除,重新加入收集代列表中,并且将计数值设置为1。这是因为父对象可以被访问,那么子对象一定可以被访问。

  1. 把定义了__del__的对象从不可达对象中移除
  1. static int
  2. has_legacy_finalizer(PyObject *op)
  3. {
  4. return Py_TYPE(op)->tp_del != NULL;
  5. }
  6. static void
  7. move_legacy_finalizers(PyGC_Head *unreachable, PyGC_Head *finalizers)
  8. {
  9. PyGC_Head *gc, *next;
  10. assert((unreachable->_gc_next & NEXT_MASK_UNREACHABLE) == 0);
  11. for (gc = GC_NEXT(unreachable); gc != unreachable; gc = next) {
  12. PyObject *op = FROM_GC(gc);
  13. _PyObject_ASSERT(op, gc->_gc_next & NEXT_MASK_UNREACHABLE);
  14. gc->_gc_next &= ~NEXT_MASK_UNREACHABLE;
  15. next = (PyGC_Head*)gc->_gc_next;
  16. if (has_legacy_finalizer(op)) {
  17. gc_clear_collecting(gc);
  18. gc_list_move(gc, finalizers);
  19. }
  20. }
  21. }

这里的逻辑就比较简单,判断是否定义了__del__函数,如果有,则从不可达列表中删除,加入finalizers列表,并且清除收集中标记。

  1. /* A traversal callback for move_legacy_finalizer_reachable. */
  2. static int
  3. visit_move(PyObject *op, PyGC_Head *tolist)
  4. {
  5. if (_PyObject_IS_GC(op)) {
  6. PyGC_Head *gc = AS_GC(op);
  7. if (gc_is_collecting(gc)) {
  8. gc_list_move(gc, tolist);
  9. gc_clear_collecting(gc);
  10. }
  11. }
  12. return 0;
  13. }
  14. /* Move objects that are reachable from finalizers, from the unreachable set
  15. * into finalizers set.
  16. */
  17. static void
  18. move_legacy_finalizer_reachable(PyGC_Head *finalizers)
  19. {
  20. traverseproc traverse;
  21. PyGC_Head *gc = GC_NEXT(finalizers);
  22. for (; gc != finalizers; gc = GC_NEXT(gc)) {
  23. /* Note that the finalizers list may grow during this. */
  24. traverse = Py_TYPE(FROM_GC(gc))->tp_traverse;
  25. (void) traverse(FROM_GC(gc),
  26. (visitproc)visit_move,
  27. (void *)finalizers);
  28. }
  29. }

然后再遍历finalizers列表中的所有对象,判断对象的每个元素是否也是可GC对象,并且也有收集中标记,如果满足条件,则从不可达列表中删除,加入finalizers列表,并且清除收集中标记。

  1. 遍历不可达对象列表,处理弱引用。
  2. 遍历不可达对象列表,为每个对象调用tp_finalize函数,如果没有则跳过。
  1. static void
  2. finalize_garbage(PyThreadState *tstate, PyGC_Head *collectable)
  3. {
  4. destructor finalize;
  5. PyGC_Head seen;
  6. gc_list_init(&seen);
  7. while (!gc_list_is_empty(collectable)) {
  8. PyGC_Head *gc = GC_NEXT(collectable);
  9. PyObject *op = FROM_GC(gc);
  10. gc_list_move(gc, &seen);
  11. if (!_PyGCHead_FINALIZED(gc) &&
  12. (finalize = Py_TYPE(op)->tp_finalize) != NULL) {
  13. _PyGCHead_SET_FINALIZED(gc);
  14. Py_INCREF(op);
  15. finalize(op);
  16. assert(!_PyErr_Occurred(tstate));
  17. Py_DECREF(op);
  18. }
  19. }
  20. gc_list_merge(&seen, collectable);
  21. }
  1. 处理复活的对象
  1. static inline void
  2. handle_resurrected_objects(PyGC_Head *unreachable, PyGC_Head* still_unreachable,
  3. PyGC_Head *old_generation)
  4. {
  5. // Remove the PREV_MASK_COLLECTING from unreachable
  6. // to prepare it for a new call to 'deduce_unreachable'
  7. gc_list_clear_collecting(unreachable);
  8. // After the call to deduce_unreachable, the 'still_unreachable' set will
  9. // have the PREV_MARK_COLLECTING set, but the objects are going to be
  10. // removed so we can skip the expense of clearing the flag.
  11. PyGC_Head* resurrected = unreachable;
  12. deduce_unreachable(resurrected, still_unreachable);
  13. clear_unreachable_mask(still_unreachable);
  14. // Move the resurrected objects to the old generation for future collection.
  15. gc_list_merge(resurrected, old_generation);
  16. }

这里主要是上一步会调用tp_finalize函数,有可能会把一些对象复活,所以需要重新收集一次不可达对象,然后将复活的对象移入老年代中。

  1. 删除不可达对象
  1. static void
  2. delete_garbage(PyThreadState *tstate, GCState *gcstate,
  3. PyGC_Head *collectable, PyGC_Head *old)
  4. {
  5. assert(!_PyErr_Occurred(tstate));
  6. while (!gc_list_is_empty(collectable)) {
  7. PyGC_Head *gc = GC_NEXT(collectable);
  8. PyObject *op = FROM_GC(gc);
  9. _PyObject_ASSERT_WITH_MSG(op, Py_REFCNT(op) > 0,
  10. "refcount is too small");
  11. if (gcstate->debug & DEBUG_SAVEALL) {
  12. assert(gcstate->garbage != NULL);
  13. if (PyList_Append(gcstate->garbage, op) < 0) {
  14. _PyErr_Clear(tstate);
  15. }
  16. }
  17. else {
  18. inquiry clear;
  19. if ((clear = Py_TYPE(op)->tp_clear) != NULL) {
  20. Py_INCREF(op);
  21. (void) clear(op);
  22. if (_PyErr_Occurred(tstate)) {
  23. _PyErr_WriteUnraisableMsg("in tp_clear of",
  24. (PyObject*)Py_TYPE(op));
  25. }
  26. Py_DECREF(op);
  27. }
  28. }
  29. if (GC_NEXT(collectable) == gc) {
  30. /* object is still alive, move it, it may die later */
  31. gc_clear_collecting(gc);
  32. gc_list_move(gc, old);
  33. }
  34. }
  35. }

其中的逻辑也简单,遍历最终不可达列表,然后调用每个对象的tp_clear函数。调用后,如果对象可以被释放,则也会从GC列表中移除。所以在后面有一个判断if (GC_NEXT(collectable) == gc),也就是该对象还没有被移除,这种情况则清除该对象的收集中标记,然后移入老年代中。

  1. finalizers列表中的对象移入老年代中
  1. static void
  2. handle_legacy_finalizers(PyThreadState *tstate,
  3. GCState *gcstate,
  4. PyGC_Head *finalizers, PyGC_Head *old)
  5. {
  6. assert(!_PyErr_Occurred(tstate));
  7. assert(gcstate->garbage != NULL);
  8. PyGC_Head *gc = GC_NEXT(finalizers);
  9. for (; gc != finalizers; gc = GC_NEXT(gc)) {
  10. PyObject *op = FROM_GC(gc);
  11. if ((gcstate->debug & DEBUG_SAVEALL) || has_legacy_finalizer(op)) {
  12. if (PyList_Append(gcstate->garbage, op) < 0) {
  13. _PyErr_Clear(tstate);
  14. break;
  15. }
  16. }
  17. }
  18. gc_list_merge(finalizers, old);
  19. }

所以说,定义了__del__的对象,有可能出现无法回收的情况。需要仔细编码。

总结

python的垃圾回收主要用到了

  1. 引用计数
  2. 标记清除
  3. 分代回收

其中分代回收步骤为

  1. 将年轻代的对象移动到指定回收代的列表后。
  2. 遍历回收代列表,将对象设置为收集中PREV_MASK_COLLECTING标记,然后将引用计数复制一份到_gc_prev中
  3. 然后遍历每个对象中的每个元素,如果这个元素也是可GC对象,并且也有收集中标记,则将_gc_prev中的计数值减1
  4. 再遍历回收代列表,判断_gc_prev计数值是否为0,
    1. 如果为0,则标记为不可达,然后移动到不可达列表中。
    2. 如果不为0,则遍历该对象的元素,如果该元素已经标记为清除,就把该元素移动到原回收代列表中。(也就是父对象仍然可达,则子对象也可达)。然后清除该对象的收集中标记。
  5. 遍历不可达列表,清除不可达标记,判断是否定义了__del__函数,如果有,则将清除收集中标记,并移入finalizers列表中。
  6. 遍历finalizers列表的每个对象,判断对象中的元素是否是可GC对象,并且有收集中标记,将该元素清除收集中标记,移入finalizers列表中。
  7. 遍历不可达列表, 处理弱引用
  8. 遍历不可达列表的每个对象,调用对象的tp_finalize函数,如果没有则跳过。
  9. 遍历不可达列表,将复活对象移到老年代列表中,其他对象移动到仍然不可达列表final_unreachable
  10. 最后遍历 final_unreachable 列表,为每个对象调用tp_clear函数
    1. 如果真的可以删除,则把自己从对应GC列表中摘除
    2. 如果还不能删除,则清除对象的收集中标记,对象重新加入老年代中。
  11. finalizers列表中的每个对象重新加入老年代列表中。

例子

说到这里好像还没有具体分析环引用的情况

  1. import sys
  2. import gc
  3. def a():
  4. lst1 = []
  5. lst2 = []
  6. lst1.append(lst2)
  7. lst2.append(lst1)
  8. print("lst1 refcnt: {}".format(sys.getrefcount(lst1)))
  9. print("lst2 refcnt: {}".format(sys.getrefcount(lst2)))
  10. before_collect_cnt = gc.collect(2)
  11. a()
  12. after_collect_cnt = gc.collect(2)
  13. print("before({}), after({})".format(before_collect_cnt, after_collect_cnt))

在笔者的电脑上输出

  1. hejs@ubuntu:~$ python main.py
  2. lst1 refcnt: 3
  3. lst2 refcnt: 3
  4. before(0), after(2)

可以看到,在执行a函数时,lst1和lst2的引用计数为2(因为sys.getrefcount也会引用一次,所以输出的值是真实计数+1)。
当a函数调用结束后,由于函数内的lst1、lst2变量解除了引用,所以此时两个列表的计数值就为1了。出现环引用,无法释放。
这个时候就轮到标记清楚和分代回收解决了。

  1. 首先会将第0、1代的元素移到第2代上。因为gc.collect(2)
  2. 然后遍历第2代列表,为每个对象设置收集中标记,将对象的真实计数复制到_gc_prev中。
  3. 再遍历第2代列表,判断对象的子元素是否也是 可GC对象、也有收集中标记,如果有则将该元素计数值减1。
    1. 此时 lst1、lst2的_gc_prev计数值都为0
  4. 然后将_gc_prev计数值为0的对象移入不可达列表中。
  5. 因为listobject没有__del__函数,也没有tp_finalize函数,所以直接到第10步,调用tp_clear函数。
  1. static int _list_clear(PyListObject *a)
  2. {
  3. Py_ssize_t i;
  4. PyObject **item = a->ob_item;
  5. if (item != NULL) {
  6. i = Py_SIZE(a);
  7. Py_SET_SIZE(a, 0);
  8. a->ob_item = NULL;
  9. a->allocated = 0;
  10. while (--i >= 0) {
  11. Py_XDECREF(item[i]);
  12. }
  13. PyMem_FREE(item);
  14. }
  15. /* Never fails; the return value can be ignored.
  16. Note that there is no guarantee that the list is actually empty
  17. at this point, because XDECREF may have populated it again! */
  18. return 0;
  19. }

也就是会为每个元素的引用计数减1。从之前分析可知,当计数减为0时,会调用对象的tp_dealloc函数,再看看listobject的tp_dealloc实现。

  1. static void
  2. list_dealloc(PyListObject *op)
  3. {
  4. Py_ssize_t i;
  5. PyObject_GC_UnTrack(op);
  6. Py_TRASHCAN_BEGIN(op, list_dealloc)
  7. if (op->ob_item != NULL) {
  8. i = Py_SIZE(op);
  9. while (--i >= 0) {
  10. Py_XDECREF(op->ob_item[i]);
  11. }
  12. PyMem_FREE(op->ob_item);
  13. }
  14. if (numfree < PyList_MAXFREELIST && PyList_CheckExact(op))
  15. free_list[numfree++] = op;
  16. else
  17. Py_TYPE(op)->tp_free((PyObject *)op);
  18. Py_TRASHCAN_END
  19. }

首先会调用PyObject_GC_UnTrack,就是将该对象从GC链表中摘除。然后再遍历子元素,将子元素的计数减1。计数减为0时,又会调用对象的tp_dealloc函数。

此番调用下来,lst1和lst2的计数都会被减为0,都会从GC链表中摘除,并且都能释放。解除了环引用。

原文链接:https://www.cnblogs.com/nisonge/p/17685664.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号