zoukankan      html  css  js  c++  java
  • Python内存管理:垃圾回收

    http://blog.csdn.net/pipisorry/article/details/39647931

    Python GC主要使用引用计数(reference counting)来跟踪和回收垃圾。在引用计数的基础上,通过“标记-清除”(mark and sweep)解决容器对象可能产生的循环引用问题,通过“分代回收”(generation collection)以空间换时间的方法提高垃圾回收效率。也就是Python中的垃圾回收是以引用计数为主,分代收集为辅。

    引用计数

    概述

    引用计数法在对象内部维护了一个被其它对象引用数的引用计数值。当这个引用计数值为0时。说明这个对象不再被其它对象引用,就能够被回收了。

    结合源代码来看,全部Python对象的头部包括了这样一个结构PyObject(相当于继承自PyObject):

    // object.h
    struct _object {
        Py_ssize_t ob_refcnt;
        struct PyTypeObject *ob_type;
    } PyObject;
    

    ob_refcnt就是引用计数值。

    比如,以下是int型对象的定义:

    // intobject.h
    typedef struct {
            PyObject_HEAD
            long ob_ival;
    } PyIntObject;
    

    引用计数的增减

        导致引用计数+1的情况
            对象被创建,比如a=23
            对象被引用,比如b=a
            对象被作为參数,传入到一个函数中,比如func(a)
            对象作为一个元素。存储在容器中,比如list1=[a,a]
        导致引用计数-1的情况
            对象的别名被显式销毁。比如del a
            对象的别名被赋予新的对象,比如a=24
            一个对象离开它的作用域,比如f函数运行完成时,func函数中的局部变量(全局变量不会)
            对象所在的容器被销毁。或从容器中删除对象

    引用计数演示样例

    def func(c):
        print 'in func function', sys.getrefcount(c) - 1

    print 'init', sys.getrefcount(11) - 1
    a = 11
    print 'after a=11', sys.getrefcount(11) - 1
    b = a
    print 'after b=1', sys.getrefcount(11) - 1
    func(11)
    print 'after func(a)', sys.getrefcount(11) - 1
    list1 = [a, 12, 14]
    print 'after list1=[a,12,14]', sys.getrefcount(11) - 1
    a=12
    print 'after a=12', sys.getrefcount(11) - 1
    del a
    print 'after del a', sys.getrefcount(11) - 1
    del b
    print 'after del b', sys.getrefcount(11) - 1
    # list1.pop(0)
    # print 'after pop list1',sys.getrefcount(11)-1
    del list1
    print 'after del list1', sys.getrefcount(11) - 1

    init 24
    after a=11 25
    after b=1 26
    in func function 28
    after func(a) 26
    after list1=[a,12,14] 27
    after a=12 26
    after del a 26
    after del b 25
    after del list1 24

    查看一个对象的引用计数:sys.getrefcount(a)能够查看a对象的引用计数。可是比正常计数大1,因为调用函数getrefcount的时候传入a。这也会让a的引用计数+1。

    循环引用导致内存泄露

    def f2():
        while True:
            c1=ClassA()
            c2=ClassA()
            c1.t=c2
            c2.t=c1
            del c1
            del c2
           

    运行f2()。进程占用的内存会不断增大。

    创建了c1,c2后,0x237cf30(c1相应的内存,记为内存1),0x237cf58(c2相应的内存,记为内存2)这两块内存的引用计数都是1,运行c1.t=c2c2.t=c1后,这两块内存的引用计数变成2。在del c1后。内存1的对象的引用计数变为1。因为不是为0,所以内存1的对象不会被销毁,所以内存2的对象的引用数依旧是2,在del c2后,同理,内存1的对象,内存2的对象的引用数都是1。
    尽管它们两个的对象都是能够被销毁的。可是因为循环引用,导致垃圾回收器都不会回收它们,所以就会导致内存泄露

    引用计数法优缺点

    引用计数法有非常明显的长处
        高效
        运行期没有停顿
        对象有确定的生命周期
        易于实现
    原始的引用计数法也有明显的缺点:
        维护引用计数的次数和引用赋值成正比,而不像mark and sweep等基本与回收的内存数量有关。


        无法解决循环引用的问题。A和B相互引用而再没有外部引用A与B中的不论什么一个,它们的引用计数都为1,但显然应该被回收。
    为了解决这两个致命弱点,Python又引入了以下两种GC机制。

    皮皮blog



    标记-清除

    “标记-清除”法是为了解决循环引用问题。能够包括其它对象引用的容器对象(如list, dict, set,甚至class)都可能产生循环引用,为此,在申请内存时,全部容器对象的头部又加上了PyGC_Head来实现“标记-清除”机制。

    // objimpl.h
    typedef union _gc_head {
        struct {
            union _gc_head *gc_next;
            union _gc_head *gc_prev;
            Py_ssize_t gc_refs;
        } gc;
        long double dummy;  /* force worst-case alignment */
    } PyGC_Head;
    垃圾标记时,先将集合中对象的引用计数复制一份副本(以免在操作过程中破坏真实的引用计数值)。然后操作这个副本,遍历对象集合,将被引用对象的引用计数副本值减1。

    然后依据引用计数副本值是否为0将集合内的对象分成两类。reachable和unreachable,当中unreachable是能够被回收的对象。在处理了weak reference和finalizer等琐碎细节后(本文不展开讲述,有兴趣的请參考python源代码),就能够回收unreachable中的对象了。

    皮皮blog



    分代回收

    分代回收的总体思想是:将系统中的全部内存块依据其存活时间划分为不同的集合。每一个集合就成为一个“代”,垃圾收集频率随着“代”的存活时间的增大而减小,存活时间通常利用经过几次垃圾回收来度量。

    用来表示“代”的结构体是gc_generation。 包括了当前代链表表头、对象数量上限、当前对象数量:

    // gcmodule.c
    struct gc_generation {
        PyGC_Head head;
        int threshold; /* collection threshold */
        int count; /* count of allocations or collections of younger
                  generations */
    };

    Python默认定义了三代对象集合。索引数越大。对象存活时间越长。

    新生成的对象会被增加第0代,前面_PyObject_GC_Malloc中省略的部分就是Python GC触发的时机。每新生成一个对象都会检查第0代有没有满。假设满了就開始着手进行垃圾回收。

    皮皮blog


    gc模块经常使用功能解析

    Garbage Collector interface
    gc模块提供一个接口给开发人员设置垃圾回收的选项。

    上面说到,採用引用计数的方法管理内存的一个缺陷是循环引用。而gc模块的一个主要功能就是解决循环引用的问题。

    应用

        项目中避免循环引用
        引入gc模块。启动gc模块的自己主动清理循环引用的对象机制
        因为分代收集,所以把须要长期使用的变量集中管理。并尽快移到二代以后,降低GC检查时的消耗
        gc模块唯一处理不了的是循环引用的类都有__del__方法,所以项目中要避免定义__del__方法,假设一定要使用该方法,同一时候导致了循环引用。须要代码显式调用gc.garbage里面的对象的__del__来打破僵局

    from:http://blog.csdn.net/pipisorry/article/details/39647931

    ref: [python的内存管理机制]

    [《Python源代码剖析》,陈儒著,2008]

    [Python垃圾回收机制具体解释]

    [Python垃圾回收机制]*


  • 相关阅读:
    bzoj4196: [Noi2015]软件包管理器
    bzoj3083: 遥远的国度
    bzoj4034: [HAOI2015]T2
    2.EXIT_KEY
    AD如何1比1打印
    编程时注意,
    同步事件、异步事件、轮询
    事件位
    挂起进程相关API
    PROCESS_EVENT_POLL事件
  • 原文地址:https://www.cnblogs.com/yangykaifa/p/7259553.html
Copyright © 2011-2022 走看看