IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Python知识库 -> 【Hard Python】【第三章-GC】2、python的GC流程 -> 正文阅读

[Python知识库]【Hard Python】【第三章-GC】2、python的GC流程

除了通过引用计数直接销毁对象之外,python还是拥有内在GC机制的,并且也有完整的一套流程。

如果只有通过引用计数销毁对象这种机制,那么随便构造一个循环引用就会造成内存泄漏,比如下面的代码:

def _dump_gc():
    gcobjs = gc.get_objects()
    pprint.pprint(len(gcobjs))


def test_circle():
    def _test_internal():
        _dump_gc()
        a = []
        b = []
        a.append(b)
        b.append(a)
        _dump_gc()

    _test_internal()
    _dump_gc()
    gc.collect()
    _dump_gc()

打印出来的结果是:

13707
13709
13709
13370

很显然,当退出_test_internal作用域时,gc对象的数量没有变化,这就说明在_test_internal里创建的a、b两个对象没有被立即释放掉。如果注释掉两个append行,就能看到打印结果第三行变成了13707,说明a、b在退出函数时就被销毁了。

所以首先,我们从循环引用入手,来研究一下pythongc机制(好巧不巧的是,pythongc也是专门为循环引用而设置的)。调用gc.collect触发gc之后,会跑到gc_collect_main触发完整的gc流程。gc_collect_mainpython整个gc流程的主入口,我们来看其中的代码:

// gcmodule.c
static Py_ssize_t
gc_collect_main(PyThreadState *tstate, int generation,
                Py_ssize_t *n_collected, Py_ssize_t *n_uncollectable,
                int nofail)
{
    int i;
    Py_ssize_t m = 0; /* # objects collected */
    Py_ssize_t n = 0; /* # unreachable objects that couldn't be collected */
    PyGC_Head *young; /* the generation we are examining */
    PyGC_Head *old; /* next older generation */
    PyGC_Head unreachable; /* non-problematic unreachable trash */
    PyGC_Head finalizers;  /* objects with, & reachable from, __del__ */
    PyGC_Head *gc;
    _PyTime_t t1 = 0;   /* initialize to prevent a compiler warning */
    GCState *gcstate = &tstate->interp->gc;

    /* update collection and allocation counters */
    if (generation+1 < NUM_GENERATIONS)
        gcstate->generations[generation+1].count += 1;
    for (i = 0; i <= generation; i++)
        gcstate->generations[i].count = 0;

    /* 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));
    }

    /* handy references */
    young = GEN_HEAD(gcstate, generation);
    if (generation < NUM_GENERATIONS-1)
        old = GEN_HEAD(gcstate, generation+1);
    else
        old = young;

    deduce_unreachable(young, &unreachable);

    untrack_tuples(young);
    /* Move reachable objects to next generation. */
    if (young != old) {
        if (generation == NUM_GENERATIONS - 2) {
            gcstate->long_lived_pending += gc_list_size(young);
        }
        gc_list_merge(young, old);
    }
    else {
        /* We only un-track dicts in full collections, to avoid quadratic
           dict build-up. See issue #14775. */
        untrack_dicts(young);
        gcstate->long_lived_pending = 0;
        gcstate->long_lived_total = gc_list_size(young);
    }

    /* All objects in unreachable are trash, but objects reachable from
     * legacy finalizers (e.g. tp_del) can't safely be deleted.
     */
    gc_list_init(&finalizers);
    // NEXT_MASK_UNREACHABLE is cleared here.
    // After move_legacy_finalizers(), unreachable is normal list.
    move_legacy_finalizers(&unreachable, &finalizers);
    /* finalizers contains the unreachable objects with a legacy finalizer;
     * unreachable objects reachable *from* those are also uncollectable,
     * and we move those into the finalizers list too.
     */
    move_legacy_finalizer_reachable(&finalizers);

    /* Clear weakrefs and invoke callbacks as necessary. */
    m += handle_weakrefs(&unreachable, old);

    validate_list(old, collecting_clear_unreachable_clear);
    validate_list(&unreachable, collecting_set_unreachable_clear);

    /* Call tp_finalize on objects which have one. */
    finalize_garbage(tstate, &unreachable);

    /* Handle any objects that may have resurrected after the call
     * to 'finalize_garbage' and continue the collection with the
     * objects that are still unreachable */
    PyGC_Head final_unreachable;
    handle_resurrected_objects(&unreachable, &final_unreachable, old);

    /* Call tp_clear on objects in the final_unreachable set.  This will cause
    * the reference cycles to be broken.  It may also cause some objects
    * in finalizers to be freed.
    */
    m += gc_list_size(&final_unreachable);
    delete_garbage(tstate, gcstate, &final_unreachable, old);

    /* Append instances in the uncollectable set to a Python
     * reachable list of garbage.  The programmer has to deal with
     * this if they insist on creating this type of structure.
     */
    handle_legacy_finalizers(tstate, gcstate, &finalizers, old);

    /* Clear free list only during the collection of the highest
     * generation */
    if (generation == NUM_GENERATIONS-1) {
        clear_freelists(tstate->interp);
    }

    if (_PyErr_Occurred(tstate)) {
        if (nofail) {
            _PyErr_Clear(tstate);
        }
        else {
            _PyErr_WriteUnraisableMsg("in garbage collection", NULL);
        }
    }

    /* Update stats */
    if (n_collected) {
        *n_collected = m;
    }
    if (n_uncollectable) {
        *n_uncollectable = n;
    }

    struct gc_generation_stats *stats = &gcstate->generation_stats[generation];
    stats->collections++;
    stats->collected += m;
    stats->uncollectable += n;

    assert(!_PyErr_Occurred(tstate));
    return n + m;
}

gc_collect_main总共执行了以下几个步骤,一个一个来说。

首先更新指定generation后的分配计数(+1),指定generation及以下的全部归0。这是因为python也自带了分代回收机制,gc.collect入参是generation,指定generation以下的全部都会被gc掉。

pythongc分代总共是3代,而gc.collect默认值是2,也表示最高一代,通俗点讲就是Full GC。每一代的对象数目如果超过特定值,就会触发自动gc。

然后做的一个事情是,将指定generation前代的和当代的所有GC_Head全部串到一个链表上,这样只需要处理这一个链表就能回收所有东西。每个python对象都会自带GC_Head,串到特定genenration的链表中,用于在GC时候被识别到。

之后做的很关键的一步是deduce_unreachable,其作用是模拟去引用的流程,探测无法从gc根对象直接达到的对象,放到单独的unreachable列表中。并且在这一步会通过遍历对象之间的引用关系并-1引用的方式,从而将对象之间的循环引用暂时归零。deduce_unreachable的代码如下:

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);
}

好比一个场景:总共有4个list对象,分别为l1l4,其中l1l2循环引用,l3l4循环引用,l1l2l3l4之间没有关联。之后若再有一个变量a引用了l1,那么经过deduce_unreachable之后,会呈现如下的结果:

  • subtract_refs步骤中,l1的引用数目变成1,l2l3l4变成0
  • move_unreachable步骤中,发现l1引用数目为1,即将l1以及其引用到的所有变量标记为reachable
    • 最后剩下来引用数仍然为0的l3l4,放到unreachable链表中

经历deduce_unreachable步骤存活下来的reachable对象,会直接被移动到下一个generation。接下来只需要考虑对unreachable的对象进行销毁了。

接下来的操作是将unreachable链表中,会处理一些C层类型定义里含有旧版tc_del方法的类型的对象,这些对象及其直接或间接引用的对象全部都会被移动到单独的finalizers链表中,而这个链表中的对象会被单独维护,无法被回收。

在以前的版本中,如果python类型定义了__del__方法,那么这些类型的对象就会移动到finalizers链表中。直到PEP442之后,__del__方法就对应了C层类型定义的另外一个tc_finalize方法了,因此包含__del__方法的类型的对象,不一定会移动到finalizers链表中,而是会在后面的步骤中触发tc_finalize逻辑。

再之后,针对剩下的unreachable对象,通过handle_weakrefs方法解除其它对象对其的弱引用。然后调用finalize_garbage销毁unreachable对象。finalize_garbage的代码如下:

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);
}

finalize_garbage实质是调用对象类型定义的tp_finalize方法析构对应的对象,并减少其引用为0从而释放对象内存。由于某些对象类型可能没有默认的tp_finalize方法,因此经过这一步骤之后,还会存留一些未销毁的对象。

针对未销毁的对象,之后会通过handle_resurrected_objects进行处理。在handle_resurrected_objects中会再一次执行deduce_unreachable模拟去引用操作,存活下来的unreachable对象就被移到下一个generation,而剩下的对象就会被移动到final_unreachable链表进行后面的销毁操作。

通过delete_garbage方法会对final_unreachable链表的对象进行销毁,其代码如下:

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);
        }
    }
}

delete_garbage中,每一个对象都会调用其类型的tp_clear方法,减少对象引用数目为0,触发对象的销毁。

delete_garbage之后,终于就会对先前单独拎出来的finalizers链表进行处理。finalizers链表中所有的内容都会通过handle_legacy_finalizers方法被移动到当前gcstategarbage链表中单独维护,不会被销毁。

之后进行一些数据清理和数据统计逻辑,整个gc流程就完成了。

  Python知识库 最新文章
Python中String模块
【Python】 14-CVS文件操作
python的panda库读写文件
使用Nordic的nrf52840实现蓝牙DFU过程
【Python学习记录】numpy数组用法整理
Python学习笔记
python字符串和列表
python如何从txt文件中解析出有效的数据
Python编程从入门到实践自学/3.1-3.2
python变量
上一篇文章      下一篇文章      查看所有文章
加:2022-03-21 20:45:38  更:2022-03-21 20:48:01 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 19:47:08-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码