一文詳解Python垃圾回收
Python版本
v3.9.17
分析代碼的過(guò)程比較枯燥,可以直接跳轉(zhuǎn)到總結(jié)。
只能被其他對(duì)象引用類型
比如:longobject、floatobject
floatobject
以floatobject為例子來(lái)分析,先看看結(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,只有在編譯時(shí)指定--with-trace-refs才有效,這里忽略即可。
./configure --with-trace-refs
可以看到在PyObject里有一個(gè)ob_refcnt的屬性,這個(gè)就是引用計(jì)數(shù)。
當(dāng)對(duì)引用計(jì)數(shù)減為0時(shí),就會(huì)調(diào)用各類型對(duì)應(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);
}
}能引用其他對(duì)象的類型
比如listobject,dictobject...
listobject
以listobject為例子來(lái)分析,先看看結(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多了一個(gè)Py_ssize_t ob_size而已,這個(gè)屬性是用來(lái)表示這個(gè)可變對(duì)象里元素?cái)?shù)量。
因?yàn)榭梢砸闷渌麑?duì)象,就有可能會(huì)出現(xiàn)環(huán)引用問(wèn)題,這種問(wèn)題如果再使用引用計(jì)數(shù)來(lái)作為GC就會(huì)出現(xiàn)問(wèn)題。
lst1 = [] lst2 = [] lst1.append(lst2) lst2.append(lst1)
當(dāng)然這種情況可以使用弱引用,或者手動(dòng)解除環(huán)引用。這些解決方案這里不深入,現(xiàn)在主要看看python是怎樣應(yīng)對(duì)這種情況。
對(duì)于這類型的對(duì)象在申請(qǐng)內(nèi)存的時(shí)候調(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;
}在可變對(duì)象中,python又加上了一個(gè)PyGC_Head。通過(guò)這個(gè)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;
}通過(guò)這里的變量名,可以猜測(cè)使用到了分代垃圾回收。
分代回收
python手動(dòng)執(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)用通過(guò)gc.callbacks注冊(cè)的回調(diào)函數(shù),這里我們忽略,重點(diǎn)分析collect函數(shù)。
collect函數(shù)簽名
這段代碼很長(zhǎng),我們拆分開來(lái)分析,這里會(huì)去除掉一些DEBUG相關(guān)的邏輯。
static Py_ssize_t collect(PyThreadState *tstate, int generation,Py_ssize_t *n_collected, Py_ssize_t *n_uncollectable, int nofail);
- 將新生代的對(duì)象合并到指定代的對(duì)象列表中。
/* 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),就表示啟動(dòng)全部的垃圾回收。這里就會(huì)將第0、1代的對(duì)象合并到第2代上。合并之后第0、1代上就空了,全部可GC的對(duì)象都在第2代上。
- 推斷不可達(dá)對(duì)象
/* 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更新引用計(jì)數(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);
}
}這里的邏輯就是遍歷所有對(duì)象,然后賦值_gc_prev,設(shè)置為收集中的標(biāo)識(shí)PREV_MASK_COLLECTING,然后將引用計(jì)數(shù)賦值給_gc_prev 。最后_gc_prev的內(nèi)容如下。

更新完_gc_prev后,就開始調(diào)用subtrace_refs,遍歷對(duì)象中的元素,判斷元素是否也是可GC對(duì)象并且有收集中標(biāo)記,如果是則減去該對(duì)象的計(jì)數(shù)。注意這里減去的是_gc_prev中的計(jì)數(shù),而不是真正的計(jì)數(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);
}
}更新計(jì)數(shù)值之后,就開始收集不可達(dá)對(duì)象,將對(duì)象移入到不可達(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;
}這段代碼的邏輯是,遍歷收集代中的所有對(duì)象,判斷對(duì)象的計(jì)數(shù)值是否為0
如果等于0,則從收集代中移除,加入不可達(dá)列表中,然后打上不可達(dá)標(biāo)記。
如果不等于0,則遍歷對(duì)象的所有元素,如果元素已經(jīng)被打上不可達(dá)標(biāo)記,則把該元素從不可達(dá)列表中移除,重新加入收集代列表中,并且將計(jì)數(shù)值設(shè)置為1。這是因?yàn)楦笇?duì)象可以被訪問(wèn),那么子對(duì)象一定可以被訪問(wèn)。
- 把定義了__del__的對(duì)象從不可達(dá)對(duì)象中移除
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);
}
}
}這里的邏輯就比較簡(jiǎn)單,判斷是否定義了__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列表中的所有對(duì)象,判斷對(duì)象的每個(gè)元素是否也是可GC對(duì)象,并且也有收集中標(biāo)記,如果滿足條件,則從不可達(dá)列表中刪除,加入finalizers列表,并且清除收集中標(biāo)記。
- 遍歷不可達(dá)對(duì)象列表,處理弱引用。
- 遍歷不可達(dá)對(duì)象列表,為每個(gè)對(duì)象調(diào)用tp_finalize函數(shù),如果沒有則跳過(guò)。
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ù)活的對(duì)象
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);
}這里主要是上一步會(huì)調(diào)用tp_finalize函數(shù),有可能會(huì)把一些對(duì)象復(fù)活,所以需要重新收集一次不可達(dá)對(duì)象,然后將復(fù)活的對(duì)象移入老年代中。
- 刪除不可達(dá)對(duì)象
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);
}
}
}其中的邏輯也簡(jiǎn)單,遍歷最終不可達(dá)列表,然后調(diào)用每個(gè)對(duì)象的tp_clear函數(shù)。調(diào)用后,如果對(duì)象可以被釋放,則也會(huì)從GC列表中移除。所以在后面有一個(gè)判斷if (GC_NEXT(collectable) == gc),也就是該對(duì)象還沒有被移除,這種情況則清除該對(duì)象的收集中標(biāo)記,然后移入老年代中。
- 將finalizers列表中的對(duì)象移入老年代中
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);
}所以說(shuō),定義了__del__的對(duì)象,有可能出現(xiàn)無(wú)法回收的情況。需要仔細(xì)編碼。
總結(jié)
python的垃圾回收主要用到了
- 引用計(jì)數(shù)
- 標(biāo)記清除
- 分代回收
其中分代回收步驟為
- 將年輕代的對(duì)象移動(dòng)到指定回收代的列表后。
- 遍歷回收代列表,將對(duì)象設(shè)置為收集中PREV_MASK_COLLECTING標(biāo)記,然后將引用計(jì)數(shù)復(fù)制一份到_gc_prev中
- 然后遍歷每個(gè)對(duì)象中的每個(gè)元素,如果這個(gè)元素也是可GC對(duì)象,并且也有收集中標(biāo)記,則將_gc_prev中的計(jì)數(shù)值減1
- 再遍歷回收代列表,判斷_gc_prev計(jì)數(shù)值是否為0,
如果為0,則標(biāo)記為不可達(dá),然后移動(dòng)到不可達(dá)列表中。
如果不為0,則遍歷該對(duì)象的元素,如果該元素已經(jīng)標(biāo)記為清除,就把該元素移動(dòng)到原回收代列表中。(也就是父對(duì)象仍然可達(dá),則子對(duì)象也可達(dá))。然后清除該對(duì)象的收集中標(biāo)記。
- 遍歷不可達(dá)列表,清除不可達(dá)標(biāo)記,判斷是否定義了__del__函數(shù),如果有,則將清除收集中標(biāo)記,并移入finalizers列表中。
- 遍歷finalizers列表的每個(gè)對(duì)象,判斷對(duì)象中的元素是否是可GC對(duì)象,并且有收集中標(biāo)記,將該元素清除收集中標(biāo)記,移入finalizers列表中。
- 遍歷不可達(dá)列表, 處理弱引用
- 遍歷不可達(dá)列表的每個(gè)對(duì)象,調(diào)用對(duì)象的tp_finalize函數(shù),如果沒有則跳過(guò)。
- 遍歷不可達(dá)列表,將復(fù)活對(duì)象移到老年代列表中,其他對(duì)象移動(dòng)到仍然不可達(dá)列表final_unreachable
- 最后遍歷 final_unreachable 列表,為每個(gè)對(duì)象調(diào)用tp_clear函數(shù)
如果真的可以刪除,則把自己從對(duì)應(yīng)GC列表中摘除
如果還不能刪除,則清除對(duì)象的收集中標(biāo)記,對(duì)象重新加入老年代中。
- 將finalizers列表中的每個(gè)對(duì)象重新加入老年代列表中。
例子
說(shuō)到這里好像還沒有具體分析環(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ù)時(shí),lst1和lst2的引用計(jì)數(shù)為2(因?yàn)閟ys.getrefcount也會(huì)引用一次,所以輸出的值是真實(shí)計(jì)數(shù)+1)。
當(dāng)a函數(shù)調(diào)用結(jié)束后,由于函數(shù)內(nèi)的lst1、lst2變量解除了引用,所以此時(shí)兩個(gè)列表的計(jì)數(shù)值就為1了。出現(xiàn)環(huán)引用,無(wú)法釋放。
這個(gè)時(shí)候就輪到標(biāo)記清楚和分代回收解決了。
- 首先會(huì)將第0、1代的元素移到第2代上。因?yàn)間c.collect(2)
- 然后遍歷第2代列表,為每個(gè)對(duì)象設(shè)置收集中標(biāo)記,將對(duì)象的真實(shí)計(jì)數(shù)復(fù)制到_gc_prev中。
- 再遍歷第2代列表,判斷對(duì)象的子元素是否也是 可GC對(duì)象、也有收集中標(biāo)記,如果有則將該元素計(jì)數(shù)值減1。
此時(shí) lst1、lst2的_gc_prev計(jì)數(shù)值都為0
- 然后將_gc_prev計(jì)數(shù)值為0的對(duì)象移入不可達(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;
}也就是會(huì)為每個(gè)元素的引用計(jì)數(shù)減1。從之前分析可知,當(dāng)計(jì)數(shù)減為0時(shí),會(huì)調(diào)用對(duì)象的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
}首先會(huì)調(diào)用PyObject_GC_UnTrack,就是將該對(duì)象從GC鏈表中摘除。然后再遍歷子元素,將子元素的計(jì)數(shù)減1。計(jì)數(shù)減為0時(shí),又會(huì)調(diào)用對(duì)象的tp_dealloc函數(shù)。
此番調(diào)用下來(lái),lst1和lst2的計(jì)數(shù)都會(huì)被減為0,都會(huì)從GC鏈表中摘除,并且都能釋放。解除了環(huán)引用。
到此這篇關(guān)于一文詳解Python垃圾回收的文章就介紹到這了,更多相關(guān)Python垃圾回收內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python簡(jiǎn)單實(shí)現(xiàn)區(qū)域生長(zhǎng)方式
今天小編就為大家分享一篇Python簡(jiǎn)單實(shí)現(xiàn)區(qū)域生長(zhǎng)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-01-01
使用Python進(jìn)行物聯(lián)網(wǎng)設(shè)備的控制與數(shù)據(jù)收集
Python作為一種高效且易于學(xué)習(xí)的編程語(yǔ)言,已經(jīng)成為開發(fā)物聯(lián)網(wǎng)應(yīng)用的首選語(yǔ)言之一,本文將探討如何使用Python進(jìn)行物聯(lián)網(wǎng)設(shè)備的控制與數(shù)據(jù)收集,并提供相應(yīng)的代碼示例,需要的朋友可以參考下2024-05-05
Python使用BeautifulSoup爬取網(wǎng)頁(yè)數(shù)據(jù)的操作步驟
在網(wǎng)絡(luò)時(shí)代,數(shù)據(jù)是最寶貴的資源之一,而爬蟲技術(shù)就是一種獲取數(shù)據(jù)的重要手段,Python 作為一門高效、易學(xué)、易用的編程語(yǔ)言,自然成為了爬蟲技術(shù)的首選語(yǔ)言之一,本文將介紹如何使用 BeautifulSoup 爬取網(wǎng)頁(yè)數(shù)據(jù),并提供詳細(xì)的代碼和注釋,幫助讀者快速上手2023-11-11
PyCharm代碼整體縮進(jìn),反向縮進(jìn)的方法
今天小編就為大家分享一篇PyCharm代碼整體縮進(jìn),反向縮進(jìn)的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-06-06
python開發(fā)之字符串string操作方法實(shí)例詳解
這篇文章主要介紹了python開發(fā)之字符串string操作方法,以實(shí)例形式較為詳細(xì)的分析了Python針對(duì)字符串的轉(zhuǎn)義、連接、換行、輸出等操作技巧,需要的朋友可以參考下2015-11-11

