十、垃圾回收

1、Python采用引用计数为主,分代收集和标记-清除为辅的策略。

2、引用计数

  • 每个对象维护一个ob_ref字段记录该对象当前被引用的次数:
    • 每当有新的引用指向该对象时,引用计数加1;
      • 对象被创建,a = 1;
      • 对象被引用,b = a;
      • 对象作为参数传入函数中,func(a);
      • 对象作为容器的元素,L= [a, 2]。
    • 每当对象的引用失效时,引用计数减1;
      • 对象的别名被显示销毁,del a;
      • 对象的别名被赋予新的对象,a = 2;
      • 对象离开作用域,如函数执行完毕,函数中的局部变量(全局变量不会);
      • 对象所在的容器被销毁,或从容器中删除对象。
    • 当对象的引用计数为0时,该对象被立即回收,对象占用的内存空间将被释放。
  • 缺点:需额外空间维护引用计数,不能解决对象的“循环引用”。

  • 创建c1,c2后,两个对象的引用计数都是1,执行 c1.t = 2 和 c2.t = c1后,引用计数变成2;
  • 在del c1 后,c1的引用计数变为1,由于不是0,所以c1对象不会被销毁,同理,在del c2后也一样;
  • 虽然这两个对象都可以被销毁,但由于循环引用,导致垃圾回收器不会回收它们,所以会导致内存泄漏。

2.1、案例

 3、分代收集

 (1)Python 将内存根据对象的存活时间划分为不同的集合,每个集合称为一个代,Python将内存分为3代,分别为年轻代(第0代)、中年代(第1代)、老年代(第2代),对应3个链表,它们的垃圾收集频率随对象的存活时间的增大而减小。

  • 以空间换时间;
  • 新创建的对象分配在年轻代,当年轻代链表的总数达到上限时,触发Python垃圾回收机制回收可以回收的对象,不能回收的对象会被移入中年代,以此类推,老年代中对象是存活时间最久的对象,甚至是存活于整个系统的声明周期内;
  • 建立在标记-清除基础上

(2)垃圾回收

三种情况会触发垃圾回收

  • 调用gc.collect();
  • 当gc模块的计数器达到阈值时。每一次计数器的增加,gc模块就会检查增加后的计数是否达到阀值的数目,如果是,就会执行对应的代数的垃圾检查,然后重置计数器。
  • 程序退出;

(3)gc模块

提供一个接口用于设置垃圾回收,一个主要功能是解决循环引用的问题。回收机制:垃圾检查+垃圾回收,主要作用是发现并处理 不可达的垃圾对象,且只要is_enable() = True时才会启动自动垃圾回收。

常用函数:

  • gc.set_debug(flags),设置gc的debug日志,一般设置为gc.DEBUG_LEAK;
  • gc.collect([generation]),显式进行垃圾回收,可输入参数,0表示只检查第1代对象;1表示检查1、2代对象;2表示检查1、2、3代对象;当不传入是,执行full collection,相当于传入2。返回不可达(unreachable objects)对象的数目。
  • gc.set_threshold(threshold0[, threshold1[, threshold2]]),设置自动执行垃圾回收的频率;
  • gc.get_count(),获取当前自动执行垃圾回收的计数器,返回长度为3的列表。

 

  • 355,指距离上一次1代垃圾检查,Python分配内存的数据减去释放内存的数目;
  • 11,指距离上一次2代垃圾检查,1代垃圾检查的次数;
  • 0,指距离上一次3代垃圾检查,2代垃圾检查的次数。

4、标记清除

基于追踪回收(tracing GC)技术实现的垃圾回收算法。主要用来处理一些容器对象,如list、dict、tuple、instance等。缺点是清除非活动对象必须顺序扫描整个堆内存。分为两个阶段:

  • 标记阶段, GC把所有的活动对象打上标记;
  • 回收阶段,回收没有标记的非活动对象

活动对象判断:

(1)对象质检通过引用(指针)连接,构成一个有向图。对象为有向图的节点,引用关系构成边;

(2)从根对象(root object)出发,沿有向边遍历对象,可达的对象对象标记为活动对象;不可达对象则标记为非活动对象;根对象是全局变量、调用栈、寄存器。

上图中,小黑圈表示根对象,从根对象出发,1可直达,则被标记,2、3可间接到达也被标记;4、5不可达,所以1、2、3是活动对象,4、5是非活动对象会被GC回收。

 

posted @ 2020-06-11 22:50  晨尛  阅读(204)  评论(0编辑  收藏  举报