zoukankan      html  css  js  c++  java
  • 【python原理解析】gc原理初步解析

    python的gc是会用到:引用计数、标记-清除和分代收集,首先说明一下什么是引用计数

    可以通过sys模块中的getrefcount()方法获取某个对象的引用计数

    python本身的数据类型有基础类型和容器类型,基础类型包含:整型、布尔型、浮点型、字符串型,容器类型就是说可以将其他对象包含在其中的类型,如:list、set、dict等都是容器类型

    以基础类型的整型为例:

    def f11():

        a = 1

        b = 1

        print sys.getrefcount(a)

        print sys.getrefcount(b)

        b = a

        print sys.getrefcount(a)

        print sys.getrefcount(b)

    运行结果:(在当时的情况下)

    150

    150

    150

    150

    增加引用计数的方式是:

    (1)使用=等号赋值

    (2)在容器对象中使用对象

    (3)作为函数参数传递的时候

    我们可以通过下面的函数看到:

    def f1():

        gc.set_debug(gc.DEBUG_STATS|gc.DEBUG_LEAK) 

        a=[] 

        b=[] 

        a.append(b) 

        print 'a refcount:',sys.getrefcount(a)  # 2 

        print 'b refcount:',sys.getrefcount(b)  # 3 

       

        del a 

        del b 

        print gc.collect()  # 0 

    结果为:

     

    那么可以看到:a和b在被赋值之后,refcount的值都变成了1,a引用了b之后,b的refcount值增加1变成了2;那么,为什么打印出来的结果分别是2和3呢?因为sys.getrefcount()的函数的时候,将a作为参数了,此时a的引用计数就会增加1,所以通过该方法打印出来的结果就是会比预期的结果多1

    那么引用计数增加之后,如何减少引用计数呢?一般的方法有:

    (1)使用del语句,可以减少一个计数

    (2)改变引用,给变量名设置别的对象

    (3)离开作用域

    那么上面的def f1()中,其实可以看到:del a和b之后,可以看到这个a和b是处于0代链表的,直接清理即可;del的作用是:它不仅会移除一个对象的引用,还会移除那个名字本身。

    接下来说明一下:循环引用的问题

    循环引用出现在:容器类对象相互包含的情况下,

    def f2():

        a=[] 

        b=[] 

        a.append(b) 

        b.append(a) 

        print 'a refcount:',sys.getrefcount(a)  # 3 

        print 'b refcount:',sys.getrefcount(b)  # 3 

        print gc.collect()

        del a 

        del b 

        print gc.collect()

    例如def f2()中的结果如下:

     

    可以看到gc.collect()在del之前和之后的结果不同,看了一下gc.collect()的含义以及返回值,(重点是:返回值是unreachable objects的数量被返回)

     

    因此分析一下def f2()中的程序可以看出,在没有调用删除a之前,程序中没有不可达对象,删除a之后,因为a和b的循环引用,导致都变成不可达对象

    那么什么是不可达对象?与之对象,可达对象是什么?是在什么情况下产生的?例如A引用B,则从A到B就会建立一个连接,从A指向B,从根节点开始(根节点一般是全局变量等)一直往下扫描,比如A引用B,B引用C,D引用C,那么A是root,则A-B-C都是可达的,但是D不是可达的,因此D就是unreachable的对象,可以释放的

    我们可以看到在refcount之后,循环引用的,最终的引用计数无法=0,基于引用计数=0的清除机制无法执行,则需要通过“标记-清除”的方式来进一步将引用计数进行确定处理,“标记-清除”的执行逻辑是:

    (1)对于每一个容器对象,都设置了gc_refs值,并将其设置为该对象的引用计数值

    (2)对于每一个容器对象,找到所有其引用的对象,将被引用的对象的gc_refs值减1

    (3)执行完(2)的所有容器对象,所有的gc_refs还大于0的对象都代表着被非容器对象引用,说明至少包含一个非循环引用,因此这些对象不能释放,就需要将其加入到另一个集合中

    (4)在(3)中不能释放的对象,如果他们引用某个对象,则这些对象也不能释放,将这些对象也加入到另一个集合中

    (5)经过上面步骤后,最终得到的就是不可达的对象,这些对象就是需要释放的对象

    那么如何释放呢?以及释放的频率以及触发时机如何控制呢?

    就要用到分代收集,在python中,会将对象分别加入到不同的收集链表中,依据规则:活的越久的对象越不是垃圾,回收的频率越低;分成了0代,1代和2代,其中越年轻的越会被清理,并且清理1代的时候0代也会清理,清理2代的时候1代和0代也会被清理

    这个可以通过调用函数:gc.get_threshold()来实现

    print gc.get_threshold()

    打印结果如下:(700, 10, 10)

    这个函数的返回值有三个,第一个返回值代表的是:从上一次收集开始,所有新增的对象减去删除的对象大于threshold0的时候就开始一次新的收集;第二个返回值代表的是:如果0代的对象被检查的次数超过了threshold1,则1代的检查就要执行;第三个返回值代表的是:1代的对象被检查超过了threshold2,则2代的检查就应该被执行

    这三个值的具体含义可以通过gc.set_threshold()方法来查看:

     

  • 相关阅读:
    年年岁岁花相似,岁岁年年竟相同
    两情相悦,亦或情投意合
    FreeBSD学习笔记1
    MySQL学习笔记2
    门户网站镜像站以及CDN技术
    候车
    MySQL学习笔记1
    JDBC | 第一章: 快速开始使用JDBC连接Mysql数据库之简单CRUD
    JDBC | 第零章: 什么是JDBC?
    JDBC | 第二章: JDBC之批量更新,添加,和删除操作
  • 原文地址:https://www.cnblogs.com/keke-xiaoxiami/p/8260529.html
Copyright © 2011-2022 走看看