Python的垃圾回收(Garbage collection,GC)机制梳理

Python的垃圾回收(Garbage collection,GC)机制梳理

python采用的是引用计数机制为主,标记-清除分代收集两张机制为辅的策略。

1. 引用计数(reference counting)

原理:python中每个对象维护一个ob_ref字段,用来记录该对象当前被引用的次数,每当新的引用指向该对象时,它的引用计数ob_ref加1,每当该对象的引用失效时计数ob_ref减1,一旦对象的引用计数为0,该对象立即被回收,对象占用的内存空间将被释放。

代码示例:

import sys

class Obj():
    pass

print('引用次数:', sys.getrefcount(Obj()))    # getrefcount()方法用于返回对象的引用计数

obj_1 = Obj()    # 生成对象
print('引用次数:', sys.getrefcount(obj_1))     

obj_2 = obj_1    # 增加引用
print('引用次数:', sys.getrefcount(obj_1))

del obj_2        # 销毁引用对象obj_2
print('引用次数:', sys.getrefcount(obj_1))

在这里插入图片描述
分析

  1. 使用sys.getrefcount()函数时,会被引用一次;
  2. Obj()不是引用,因此输出为1,即由于sys.getrefcount()函数而被引用的次数;
  3. obj_1引用Obj(),初始化引用次数为1,sys.getrefcount()函数调用,引用次数+1,因此结果为2;
  4. 增加引用obj_2后,引用次数+1,为3;销毁引用对象obj_2后,引用次数+1,为2。

其他

print('引用次数:', sys.getrefcount(1))
print('引用次数:', sys.getrefcount('a'))

在这里插入图片描述

  1. 小整数池和字母,因为他们在系统属于常驻内存,为公用对象,所以输出的结果 比较特殊。

总结

  1. 导致引用计数+1的情况:
    • 对象被创建,如 var_1 = 1
    • 对象被引用,如 var_2 = var_1
    • 对象被作为参数,传入到一个函数中,如 function(var_1)
    • 对象作为一个元素,存储在容器中,如 list_1 = [var_1]
  2. 导致引用计数-1的情况:
    • 对象的别名被显式销毁,如 del var_1
    • 对象的别名被赋予新的对象,如 var_1 = 2
    • 一个对象离开它的作用域,如function()函数执行完毕时,function()函数中的局部变量(全局变量不会)
    • 对象所在的容器被销毁,或从容器中删除对象

引用计数机制的优点:

  1. 简单;
  2. 实时性:一旦没有引用,内存就直接释放了(处理回收内存的时间分摊到了平时)

引用计数机制的缺点:

  1. 不好实现

    对每个对象内部留一些空间来处理引用数(空间代价);

    每个简单操作(如修改变量或引用)由于要处理计数会变复杂。

  2. 速度较慢

    不停地更新着众多引用数值,当不再使用一个大数据结构的时候(如一个包含很多元素的列表)Python可能必须一次性释放大量对象,减少引用数就成了一项复杂的递归过程了。

  3. 不能处理循环引用的情况:

    obj_1 = Obj()
    obj_2 = Obj()
    
    obj_1.next = obj_2    # Python可以动态定义实例变量或对象属性
    obj_2.prev = obj_1
    
    del obj_1
    del obj_2
    

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

注意:

针对引用计数机制的缺点3(不能处理循环引用的情况),解决方案为是建立在标记-清除基础之上的分代回收。标记-清除和分代回收计数主要处理容器对象,比如list、dict、tuple,instance(实例)等。

示例:

list1 = []
list2 = []
list1.append(list2)
list2.append(list1)

2. 标记-清除(mark and sweep)

标记清除算法是一种基于追踪回收(tracing GC)技术实现的垃圾回收算法。

两个阶段:

  1. 阶段一:标记,GC会把所有的活动对象打上标记;
  2. 阶段二:把那些没有标记的对象非活动对象进行回收。

说明:对象之间通过引用(指针)连在一起,构成一个有向图,对象构成这个有向图的节点,而引用关系构成这个有向图的边。从根对象(全局变量、调用栈、寄存器等)出发,沿着有向边遍历对象,将可到达的对象标记为活动对象,不可到达的对象就是要被清除的非活动对象。

示例:
在这里插入图片描述
分析:上图中,从根节点出发,可到达的有对象1、对象2、和对象3,但是对象4和对象5是不可达的,那么1、2、3就是活动对象,4和5是非活动对象即将被回收。

引用计数机制的优点:

  1. 简单粗暴。

标记-清除机制的缺点:

  1. 清除非活动的对象前必须顺序扫描整个堆内存,哪怕只剩下小部分活动对象也要扫描所有对象。

其他:

  1. 标记清除算法是一个深度搜索的遍历过程,但这个回收过程在Python中并不会花费很长世间,因为绝大部分对象已经通过引用计数回收了。所有没有被引用计数回收而进入标记清除环节的对象基本为循环引用的对象。

3. 分代回收

说明:

  1. 分代回收是一种以空间换时间的操作方式,Python将内存根据对象的存活时间划分为不同的集合,每个集合称为一个代。Python将内存分为了3“代”,分别为年轻代(第0代,零代Generation Zero)、中年代(第1代)、老年代(第2代),垃圾收集频率随对象的存活时间的增大而减小。
  2. 新创建的对象都会分配在年轻代,年轻代链表的总数达到上限时,Python垃圾收集机制就会被触发,把那些可以被回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,依此类推,老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内。
  3. 默认情况下,当0代超过700,或1,2代超过10(阈值),垃圾回收机制将触发。回收当“代”对应的内存和所有比它年轻的“代”对应的内存,即0代触发将清理所有三代,1代触发会清理1,2代,2代触发后只会清理自己。

4. Python垃圾回收的整体逻辑

分配内存----------------------------------------------------------------------------------(创建对象)
-> 发现超过阈值了------------------------------------------------------------------ ----(某代链表达到上限)
-> 触发垃圾回收机制
-> 将所有可收集对象链表放到一起---------------------------------------------------(合并将当代和比它年轻代对象)
-> 遍历, 计算有效引用计数
-> 分成 有效引用计数=0 和 有效引用计数 > 0 两个集合
-> 大于0的, 放入到更老一代
-> =0的, 执行回收-----------------------------------------------------------------------(引用计数)
-> 回收遍历容器内的各个元素, 减掉对应元素引用计数(破掉循环引用)
-> 执行-1的逻辑, 若发现对象引用计数=0, 触发内存回收-------------------------(标记清除)

---------------------------------------------------------------------------------------------(分代回收)

参考资料:

[Python垃圾回收机制详解][https://blog.csdn.net/xiongchengluo1129/article/details/80462651]

[Python垃圾回收机制–完美讲解!][https://www.cnblogs.com/pinganzi/p/6646742.html]

[记一次面试问题——Python 垃圾回收机制][https://testerhome.com/topics/16556]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值