一文詳解Python垃圾回收
Python版本
v3.9.17
分析代碼的過程比較枯燥,可以直接跳轉(zhuǎn)到總結(jié)。
只能被其他對象引用類型
比如:longobject、floatobject
floatobject
以floatobject為例子來分析,先看看結(jié)構(gòu)定義
typedef struct { PyObject_HEAD double ob_fval; } PyFloatObject; // 展開PyObject_HEAD后 typedef struct { PyObject ob_base; double ob_fval; } PyFloatObject; typedef struct _object { _PyObject_HEAD_EXTRA Py_ssize_t ob_refcnt; PyTypeObject *ob_type; } PyObject;
在PyObject中的_PyObject_HEAD_EXTRA
,只有在編譯時指定--with-trace-refs
才有效,這里忽略即可。
./configure --with-trace-refs
可以看到在PyObject里有一個ob_refcnt
的屬性,這個就是引用計數(shù)。
當(dāng)對引用計數(shù)減為0時,就會調(diào)用各類型對應(yīng)的析構(gòu)函數(shù)。
define Py_DECREF(op) _Py_DECREF(_PyObject_CAST(op)) void _Py_Dealloc(PyObject *op) { destructor dealloc = Py_TYPE(op)->tp_dealloc; (*dealloc)(op); } static inline void _Py_DECREF(PyObject *op) { if (--op->ob_refcnt != 0) { } else { _Py_Dealloc(op); } }
能引用其他對象的類型
比如listobject,dictobject...
listobject
以listobject為例子來分析,先看看結(jié)構(gòu)定義
typedef struct { PyObject_VAR_HEAD PyObject **ob_item; Py_ssize_t allocated; } PyListObject; // 展開 PyObject_VAR_HEAD typedef struct { PyVarObject ob_base; PyObject **ob_item; Py_ssize_t allocated; } PyListObject; typedef struct { PyObject ob_base; Py_ssize_t ob_size; /* Number of items in variable part */ } PyVarObject;
可以看出,PyObject_VAR_HEAD
也就比PyObject_HEAD
多了一個Py_ssize_t ob_size
而已,這個屬性是用來表示這個可變對象里元素數(shù)量。
因?yàn)榭梢砸闷渌麑ο?,就有可能會出現(xiàn)環(huán)引用問題,這種問題如果再使用引用計數(shù)來作為GC就會出現(xiàn)問題。
lst1 = [] lst2 = [] lst1.append(lst2) lst2.append(lst1)
當(dāng)然這種情況可以使用弱引用,或者手動解除環(huán)引用。這些解決方案這里不深入,現(xiàn)在主要看看python是怎樣應(yīng)對這種情況。
對于這類型的對象在申請內(nèi)存的時候調(diào)用的是PyObject_GC_New
,而不可變類型是用PyObject_MALLOC
。為了減少篇幅,刪掉了一些判斷邏輯。
typedef struct { // Pointer to next object in the list. // 0 means the object is not tracked uintptr_t _gc_next; // Pointer to previous object in the list. // Lowest two bits are used for flags documented later. uintptr_t _gc_prev; } PyGC_Head; #define FROM_GC(g) ((PyObject *)(((PyGC_Head *)g)+1)) static PyObject * _PyObject_GC_Alloc(int use_calloc, size_t basicsize) { PyThreadState *tstate = _PyThreadState_GET(); GCState *gcstate = &tstate->interp->gc; size_t size = sizeof(PyGC_Head) + basicsize; PyGC_Head *g; g = (PyGC_Head *)PyObject_Malloc(size); g->_gc_next = 0; g->_gc_prev = 0; gcstate->generations[0].count++; /* number of allocated GC objects */ if (/* 判斷是否可以執(zhí)行GC */) { gcstate->collecting = 1; collect_generations(tstate); gcstate->collecting = 0; } PyObject *op = FROM_GC(g); return op; }
在可變對象中,python又加上了一個PyGC_Head
。通過這個PyGC_Head
將listobject鏈接到gc列表中。
在分配完listobject內(nèi)存后,緊接著調(diào)用_PyObject_GC_TRACK
,鏈接到gc列表中。
static inline void _PyObject_GC_TRACK_impl(const char *filename, int lineno, PyObject *op) { PyGC_Head *gc = _Py_AS_GC(op); PyThreadState *tstate = _PyThreadState_GET(); PyGC_Head *generation0 = tstate->interp->gc.generation0; PyGC_Head *last = (PyGC_Head*)(generation0->_gc_prev); _PyGCHead_SET_NEXT(last, gc); _PyGCHead_SET_PREV(gc, last); _PyGCHead_SET_NEXT(gc, generation0); generation0->_gc_prev = (uintptr_t)gc; }
通過這里的變量名,可以猜測使用到了分代垃圾回收。
分代回收
python手動執(zhí)行垃圾回收一般調(diào)用gc.collect(generation=2)函數(shù)。
#define NUM_GENERATIONS 3 #define GC_COLLECT_METHODDEF \ {"collect", (PyCFunction)(void(*)(void))gc_collect, METH_FASTCALL|METH_KEYWORDS, gc_collect__doc__}, static PyObject * gc_collect(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; int generation = NUM_GENERATIONS - 1; Py_ssize_t _return_value; _return_value = gc_collect_impl(module, generation); if ((_return_value == -1) && PyErr_Occurred()) { goto exit; } return_value = PyLong_FromSsize_t(_return_value); exit: return return_value; }
具體執(zhí)行在gc_collect_impl
函數(shù)中,接著往下
static Py_ssize_t gc_collect_impl(PyObject *module, int generation) { PyThreadState *tstate = _PyThreadState_GET(); GCState *gcstate = &tstate->interp->gc; Py_ssize_t n; if (gcstate->collecting) { /* already collecting, don't do anything */ n = 0; } else { gcstate->collecting = 1; n = collect_with_callback(tstate, generation); gcstate->collecting = 0; } return n; }
可以看到,如果已經(jīng)在執(zhí)行GC,則直接返回。接著看collect_with_callback
static Py_ssize_t collect_with_callback(PyThreadState *tstate, int generation) { assert(!_PyErr_Occurred(tstate)); Py_ssize_t result, collected, uncollectable; invoke_gc_callback(tstate, "start", generation, 0, 0); result = collect(tstate, generation, &collected, &uncollectable, 0); invoke_gc_callback(tstate, "stop", generation, collected, uncollectable); assert(!_PyErr_Occurred(tstate)); return result; }
其中invoke_gc_callback
是調(diào)用通過gc.callbacks
注冊的回調(diào)函數(shù),這里我們忽略,重點(diǎn)分析collect
函數(shù)。
collect函數(shù)簽名
這段代碼很長,我們拆分開來分析,這里會去除掉一些DEBUG相關(guān)的邏輯。
static Py_ssize_t collect(PyThreadState *tstate, int generation,Py_ssize_t *n_collected, Py_ssize_t *n_uncollectable, int nofail);
- 將新生代的對象合并到指定代的對象列表中。
/* merge younger generations with one we are currently collecting */ for (i = 0; i < generation; i++) { gc_list_merge(GEN_HEAD(gcstate, i), GEN_HEAD(gcstate, generation)); }
比如調(diào)用gc.collect(2)
,就表示啟動全部的垃圾回收。這里就會將第0、1代的對象合并到第2代上。合并之后第0、1代上就空了,全部可GC的對象都在第2代上。
- 推斷不可達(dá)對象
/* handy references */ young = GEN_HEAD(gcstate, generation); if (generation < NUM_GENERATIONS-1) old = GEN_HEAD(gcstate, generation+1); else old = young; validate_list(old, collecting_clear_unreachable_clear); deduce_unreachable(young, &unreachable);
這里的young指針指向第2代的鏈表頭,validate_list
做校驗(yàn),這里忽略,重點(diǎn)在deduce_unreachable
函數(shù)中。
static inline void deduce_unreachable(PyGC_Head *base, PyGC_Head *unreachable) { validate_list(base, collecting_clear_unreachable_clear); update_refs(base); // gc_prev is used for gc_refs subtract_refs(base); gc_list_init(unreachable); move_unreachable(base, unreachable); // gc_prev is pointer again validate_list(base, collecting_clear_unreachable_clear); validate_list(unreachable, collecting_set_unreachable_set); }
首先調(diào)用update_refs更新引用計數(shù)
static inline void gc_reset_refs(PyGC_Head *g, Py_ssize_t refs) { g->_gc_prev = (g->_gc_prev & _PyGC_PREV_MASK_FINALIZED) | PREV_MASK_COLLECTING | ((uintptr_t)(refs) << _PyGC_PREV_SHIFT); } static void update_refs(PyGC_Head *containers) { PyGC_Head *gc = GC_NEXT(containers); for (; gc != containers; gc = GC_NEXT(gc)) { gc_reset_refs(gc, Py_REFCNT(FROM_GC(gc))); _PyObject_ASSERT(FROM_GC(gc), gc_get_refs(gc) != 0); } }
這里的邏輯就是遍歷所有對象,然后賦值_gc_prev,設(shè)置為收集中的標(biāo)識PREV_MASK_COLLECTING,然后將引用計數(shù)賦值給_gc_prev 。最后_gc_prev的內(nèi)容如下。
更新完_gc_prev后,就開始調(diào)用subtrace_refs,遍歷對象中的元素,判斷元素是否也是可GC對象并且有收集中標(biāo)記,如果是則減去該對象的計數(shù)。注意這里減去的是_gc_prev中的計數(shù),而不是真正的計數(shù)ob_refcnt。
static int visit_decref(PyObject *op, void *parent) { _PyObject_ASSERT(_PyObject_CAST(parent), !_PyObject_IsFreed(op)); if (_PyObject_IS_GC(op)) { PyGC_Head *gc = AS_GC(op); /* We're only interested in gc_refs for objects in the * generation being collected, which can be recognized * because only they have positive gc_refs. */ if (gc_is_collecting(gc)) { gc_decref(gc); } } return 0; } static void subtract_refs(PyGC_Head *containers) { traverseproc traverse; PyGC_Head *gc = GC_NEXT(containers); for (; gc != containers; gc = GC_NEXT(gc)) { PyObject *op = FROM_GC(gc); traverse = Py_TYPE(op)->tp_traverse; (void) traverse(FROM_GC(gc), (visitproc)visit_decref, op); } }
更新計數(shù)值之后,就開始收集不可達(dá)對象,將對象移入到不可達(dá)列表中。unreachable。
/* A traversal callback for move_unreachable. */ static int visit_reachable(PyObject *op, PyGC_Head *reachable) { if (!_PyObject_IS_GC(op)) { return 0; } PyGC_Head *gc = AS_GC(op); const Py_ssize_t gc_refs = gc_get_refs(gc); if (! gc_is_collecting(gc)) { return 0; } assert(gc->_gc_next != 0); if (gc->_gc_next & NEXT_MASK_UNREACHABLE) { PyGC_Head *prev = GC_PREV(gc); PyGC_Head *next = (PyGC_Head*)(gc->_gc_next & ~NEXT_MASK_UNREACHABLE); _PyObject_ASSERT(FROM_GC(prev), prev->_gc_next & NEXT_MASK_UNREACHABLE); _PyObject_ASSERT(FROM_GC(next), next->_gc_next & NEXT_MASK_UNREACHABLE); prev->_gc_next = gc->_gc_next; // copy NEXT_MASK_UNREACHABLE _PyGCHead_SET_PREV(next, prev); gc_list_append(gc, reachable); gc_set_refs(gc, 1); } else if (gc_refs == 0) { gc_set_refs(gc, 1); } else { _PyObject_ASSERT_WITH_MSG(op, gc_refs > 0, "refcount is too small"); } return 0; } static void move_unreachable(PyGC_Head *young, PyGC_Head *unreachable) { PyGC_Head *prev = young; PyGC_Head *gc = GC_NEXT(young); while (gc != young) { if (gc_get_refs(gc)) { PyObject *op = FROM_GC(gc); traverseproc traverse = Py_TYPE(op)->tp_traverse; _PyObject_ASSERT_WITH_MSG(op, gc_get_refs(gc) > 0, "refcount is too small"); (void) traverse(op, (visitproc)visit_reachable, (void *)young); _PyGCHead_SET_PREV(gc, prev); gc_clear_collecting(gc); prev = gc; } else { prev->_gc_next = gc->_gc_next; PyGC_Head *last = GC_PREV(unreachable); last->_gc_next = (NEXT_MASK_UNREACHABLE | (uintptr_t)gc); _PyGCHead_SET_PREV(gc, last); gc->_gc_next = (NEXT_MASK_UNREACHABLE | (uintptr_t)unreachable); unreachable->_gc_prev = (uintptr_t)gc; } gc = (PyGC_Head*)prev->_gc_next; } // young->_gc_prev must be last element remained in the list. young->_gc_prev = (uintptr_t)prev; // don't let the pollution of the list head's next pointer leak unreachable->_gc_next &= ~NEXT_MASK_UNREACHABLE; }
這段代碼的邏輯是,遍歷收集代中的所有對象,判斷對象的計數(shù)值是否為0
如果等于0,則從收集代中移除,加入不可達(dá)列表中,然后打上不可達(dá)標(biāo)記。
如果不等于0,則遍歷對象的所有元素,如果元素已經(jīng)被打上不可達(dá)標(biāo)記,則把該元素從不可達(dá)列表中移除,重新加入收集代列表中,并且將計數(shù)值設(shè)置為1。這是因?yàn)楦笇ο罂梢员辉L問,那么子對象一定可以被訪問。
- 把定義了__del__的對象從不可達(dá)對象中移除
static int has_legacy_finalizer(PyObject *op) { return Py_TYPE(op)->tp_del != NULL; } static void move_legacy_finalizers(PyGC_Head *unreachable, PyGC_Head *finalizers) { PyGC_Head *gc, *next; assert((unreachable->_gc_next & NEXT_MASK_UNREACHABLE) == 0); for (gc = GC_NEXT(unreachable); gc != unreachable; gc = next) { PyObject *op = FROM_GC(gc); _PyObject_ASSERT(op, gc->_gc_next & NEXT_MASK_UNREACHABLE); gc->_gc_next &= ~NEXT_MASK_UNREACHABLE; next = (PyGC_Head*)gc->_gc_next; if (has_legacy_finalizer(op)) { gc_clear_collecting(gc); gc_list_move(gc, finalizers); } } }
這里的邏輯就比較簡單,判斷是否定義了__del__函數(shù),如果有,則從不可達(dá)列表中刪除,加入finalizers列表,并且清除收集中標(biāo)記。
/* A traversal callback for move_legacy_finalizer_reachable. */ static int visit_move(PyObject *op, PyGC_Head *tolist) { if (_PyObject_IS_GC(op)) { PyGC_Head *gc = AS_GC(op); if (gc_is_collecting(gc)) { gc_list_move(gc, tolist); gc_clear_collecting(gc); } } return 0; } /* Move objects that are reachable from finalizers, from the unreachable set * into finalizers set. */ static void move_legacy_finalizer_reachable(PyGC_Head *finalizers) { traverseproc traverse; PyGC_Head *gc = GC_NEXT(finalizers); for (; gc != finalizers; gc = GC_NEXT(gc)) { /* Note that the finalizers list may grow during this. */ traverse = Py_TYPE(FROM_GC(gc))->tp_traverse; (void) traverse(FROM_GC(gc), (visitproc)visit_move, (void *)finalizers); } }
然后再遍歷finalizers列表中的所有對象,判斷對象的每個元素是否也是可GC對象,并且也有收集中標(biāo)記,如果滿足條件,則從不可達(dá)列表中刪除,加入finalizers列表,并且清除收集中標(biāo)記。
- 遍歷不可達(dá)對象列表,處理弱引用。
- 遍歷不可達(dá)對象列表,為每個對象調(diào)用tp_finalize函數(shù),如果沒有則跳過。
static void finalize_garbage(PyThreadState *tstate, PyGC_Head *collectable) { destructor finalize; PyGC_Head seen; gc_list_init(&seen); while (!gc_list_is_empty(collectable)) { PyGC_Head *gc = GC_NEXT(collectable); PyObject *op = FROM_GC(gc); gc_list_move(gc, &seen); if (!_PyGCHead_FINALIZED(gc) && (finalize = Py_TYPE(op)->tp_finalize) != NULL) { _PyGCHead_SET_FINALIZED(gc); Py_INCREF(op); finalize(op); assert(!_PyErr_Occurred(tstate)); Py_DECREF(op); } } gc_list_merge(&seen, collectable); }
- 處理復(fù)活的對象
static inline void handle_resurrected_objects(PyGC_Head *unreachable, PyGC_Head* still_unreachable, PyGC_Head *old_generation) { // Remove the PREV_MASK_COLLECTING from unreachable // to prepare it for a new call to 'deduce_unreachable' gc_list_clear_collecting(unreachable); // After the call to deduce_unreachable, the 'still_unreachable' set will // have the PREV_MARK_COLLECTING set, but the objects are going to be // removed so we can skip the expense of clearing the flag. PyGC_Head* resurrected = unreachable; deduce_unreachable(resurrected, still_unreachable); clear_unreachable_mask(still_unreachable); // Move the resurrected objects to the old generation for future collection. gc_list_merge(resurrected, old_generation); }
這里主要是上一步會調(diào)用tp_finalize
函數(shù),有可能會把一些對象復(fù)活,所以需要重新收集一次不可達(dá)對象,然后將復(fù)活的對象移入老年代中。
- 刪除不可達(dá)對象
static void delete_garbage(PyThreadState *tstate, GCState *gcstate, PyGC_Head *collectable, PyGC_Head *old) { assert(!_PyErr_Occurred(tstate)); while (!gc_list_is_empty(collectable)) { PyGC_Head *gc = GC_NEXT(collectable); PyObject *op = FROM_GC(gc); _PyObject_ASSERT_WITH_MSG(op, Py_REFCNT(op) > 0, "refcount is too small"); if (gcstate->debug & DEBUG_SAVEALL) { assert(gcstate->garbage != NULL); if (PyList_Append(gcstate->garbage, op) < 0) { _PyErr_Clear(tstate); } } else { inquiry clear; if ((clear = Py_TYPE(op)->tp_clear) != NULL) { Py_INCREF(op); (void) clear(op); if (_PyErr_Occurred(tstate)) { _PyErr_WriteUnraisableMsg("in tp_clear of", (PyObject*)Py_TYPE(op)); } Py_DECREF(op); } } if (GC_NEXT(collectable) == gc) { /* object is still alive, move it, it may die later */ gc_clear_collecting(gc); gc_list_move(gc, old); } } }
其中的邏輯也簡單,遍歷最終不可達(dá)列表,然后調(diào)用每個對象的tp_clear函數(shù)。調(diào)用后,如果對象可以被釋放,則也會從GC列表中移除。所以在后面有一個判斷if (GC_NEXT(collectable) == gc),也就是該對象還沒有被移除,這種情況則清除該對象的收集中標(biāo)記,然后移入老年代中。
- 將finalizers列表中的對象移入老年代中
static void handle_legacy_finalizers(PyThreadState *tstate, GCState *gcstate, PyGC_Head *finalizers, PyGC_Head *old) { assert(!_PyErr_Occurred(tstate)); assert(gcstate->garbage != NULL); PyGC_Head *gc = GC_NEXT(finalizers); for (; gc != finalizers; gc = GC_NEXT(gc)) { PyObject *op = FROM_GC(gc); if ((gcstate->debug & DEBUG_SAVEALL) || has_legacy_finalizer(op)) { if (PyList_Append(gcstate->garbage, op) < 0) { _PyErr_Clear(tstate); break; } } } gc_list_merge(finalizers, old); }
所以說,定義了__del__的對象,有可能出現(xiàn)無法回收的情況。需要仔細(xì)編碼。
總結(jié)
python的垃圾回收主要用到了
- 引用計數(shù)
- 標(biāo)記清除
- 分代回收
其中分代回收步驟為
- 將年輕代的對象移動到指定回收代的列表后。
- 遍歷回收代列表,將對象設(shè)置為收集中PREV_MASK_COLLECTING標(biāo)記,然后將引用計數(shù)復(fù)制一份到_gc_prev中
- 然后遍歷每個對象中的每個元素,如果這個元素也是可GC對象,并且也有收集中標(biāo)記,則將_gc_prev中的計數(shù)值減1
- 再遍歷回收代列表,判斷_gc_prev計數(shù)值是否為0,
如果為0,則標(biāo)記為不可達(dá),然后移動到不可達(dá)列表中。
如果不為0,則遍歷該對象的元素,如果該元素已經(jīng)標(biāo)記為清除,就把該元素移動到原回收代列表中。(也就是父對象仍然可達(dá),則子對象也可達(dá))。然后清除該對象的收集中標(biāo)記。
- 遍歷不可達(dá)列表,清除不可達(dá)標(biāo)記,判斷是否定義了__del__函數(shù),如果有,則將清除收集中標(biāo)記,并移入finalizers列表中。
- 遍歷finalizers列表的每個對象,判斷對象中的元素是否是可GC對象,并且有收集中標(biāo)記,將該元素清除收集中標(biāo)記,移入finalizers列表中。
- 遍歷不可達(dá)列表, 處理弱引用
- 遍歷不可達(dá)列表的每個對象,調(diào)用對象的tp_finalize函數(shù),如果沒有則跳過。
- 遍歷不可達(dá)列表,將復(fù)活對象移到老年代列表中,其他對象移動到仍然不可達(dá)列表final_unreachable
- 最后遍歷 final_unreachable 列表,為每個對象調(diào)用tp_clear函數(shù)
如果真的可以刪除,則把自己從對應(yīng)GC列表中摘除
如果還不能刪除,則清除對象的收集中標(biāo)記,對象重新加入老年代中。
- 將finalizers列表中的每個對象重新加入老年代列表中。
例子
說到這里好像還沒有具體分析環(huán)引用的情況
import sys import gc def a(): lst1 = [] lst2 = [] lst1.append(lst2) lst2.append(lst1) print("lst1 refcnt: {}".format(sys.getrefcount(lst1))) print("lst2 refcnt: {}".format(sys.getrefcount(lst2))) before_collect_cnt = gc.collect(2) a() after_collect_cnt = gc.collect(2) print("before({}), after({})".format(before_collect_cnt, after_collect_cnt))
在筆者的電腦上輸出
hejs@ubuntu:~$ python main.py lst1 refcnt: 3 lst2 refcnt: 3 before(0), after(2)
可以看到,在執(zhí)行a函數(shù)時,lst1和lst2的引用計數(shù)為2(因?yàn)閟ys.getrefcount也會引用一次,所以輸出的值是真實(shí)計數(shù)+1)。
當(dāng)a函數(shù)調(diào)用結(jié)束后,由于函數(shù)內(nèi)的lst1、lst2變量解除了引用,所以此時兩個列表的計數(shù)值就為1了。出現(xiàn)環(huán)引用,無法釋放。
這個時候就輪到標(biāo)記清楚和分代回收解決了。
- 首先會將第0、1代的元素移到第2代上。因?yàn)間c.collect(2)
- 然后遍歷第2代列表,為每個對象設(shè)置收集中標(biāo)記,將對象的真實(shí)計數(shù)復(fù)制到_gc_prev中。
- 再遍歷第2代列表,判斷對象的子元素是否也是 可GC對象、也有收集中標(biāo)記,如果有則將該元素計數(shù)值減1。
此時 lst1、lst2的_gc_prev計數(shù)值都為0
- 然后將_gc_prev計數(shù)值為0的對象移入不可達(dá)列表中。
- 因?yàn)閘istobject沒有__del__函數(shù),也沒有tp_finalize函數(shù),所以直接到第10步,調(diào)用tp_clear函數(shù)。
static int _list_clear(PyListObject *a) { Py_ssize_t i; PyObject **item = a->ob_item; if (item != NULL) { i = Py_SIZE(a); Py_SET_SIZE(a, 0); a->ob_item = NULL; a->allocated = 0; while (--i >= 0) { Py_XDECREF(item[i]); } PyMem_FREE(item); } /* Never fails; the return value can be ignored. Note that there is no guarantee that the list is actually empty at this point, because XDECREF may have populated it again! */ return 0; }
也就是會為每個元素的引用計數(shù)減1。從之前分析可知,當(dāng)計數(shù)減為0時,會調(diào)用對象的tp_dealloc函數(shù),再看看listobject的tp_dealloc實(shí)現(xiàn)。
static void list_dealloc(PyListObject *op) { Py_ssize_t i; PyObject_GC_UnTrack(op); Py_TRASHCAN_BEGIN(op, list_dealloc) if (op->ob_item != NULL) { i = Py_SIZE(op); while (--i >= 0) { Py_XDECREF(op->ob_item[i]); } PyMem_FREE(op->ob_item); } if (numfree < PyList_MAXFREELIST && PyList_CheckExact(op)) free_list[numfree++] = op; else Py_TYPE(op)->tp_free((PyObject *)op); Py_TRASHCAN_END }
首先會調(diào)用PyObject_GC_UnTrack,就是將該對象從GC鏈表中摘除。然后再遍歷子元素,將子元素的計數(shù)減1。計數(shù)減為0時,又會調(diào)用對象的tp_dealloc函數(shù)。
此番調(diào)用下來,lst1和lst2的計數(shù)都會被減為0,都會從GC鏈表中摘除,并且都能釋放。解除了環(huán)引用。
到此這篇關(guān)于一文詳解Python垃圾回收的文章就介紹到這了,更多相關(guān)Python垃圾回收內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python簡單實(shí)現(xiàn)區(qū)域生長方式
今天小編就為大家分享一篇Python簡單實(shí)現(xiàn)區(qū)域生長方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-01-01使用Python進(jìn)行物聯(lián)網(wǎng)設(shè)備的控制與數(shù)據(jù)收集
Python作為一種高效且易于學(xué)習(xí)的編程語言,已經(jīng)成為開發(fā)物聯(lián)網(wǎng)應(yīng)用的首選語言之一,本文將探討如何使用Python進(jìn)行物聯(lián)網(wǎng)設(shè)備的控制與數(shù)據(jù)收集,并提供相應(yīng)的代碼示例,需要的朋友可以參考下2024-05-05Python使用BeautifulSoup爬取網(wǎng)頁數(shù)據(jù)的操作步驟
在網(wǎng)絡(luò)時代,數(shù)據(jù)是最寶貴的資源之一,而爬蟲技術(shù)就是一種獲取數(shù)據(jù)的重要手段,Python 作為一門高效、易學(xué)、易用的編程語言,自然成為了爬蟲技術(shù)的首選語言之一,本文將介紹如何使用 BeautifulSoup 爬取網(wǎng)頁數(shù)據(jù),并提供詳細(xì)的代碼和注釋,幫助讀者快速上手2023-11-11PyCharm代碼整體縮進(jìn),反向縮進(jìn)的方法
今天小編就為大家分享一篇PyCharm代碼整體縮進(jìn),反向縮進(jìn)的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-06-06python開發(fā)之字符串string操作方法實(shí)例詳解
這篇文章主要介紹了python開發(fā)之字符串string操作方法,以實(shí)例形式較為詳細(xì)的分析了Python針對字符串的轉(zhuǎn)義、連接、換行、輸出等操作技巧,需要的朋友可以參考下2015-11-11