python垃圾回收机制

python垃圾回收机制

引用计数

python中,主要依靠gc模块的引用计数来进行垃圾回收,以标记-清除和分代回收作为辅助手段。所谓引用计数就是给所有的对象维护一个引用计数的属性,在一个引用被创建或复制的时候,python就把相关对象的引用计数+1:相反,当引用被销毁的时候就把相关对象的引用计数-1。当某个对象的引用计数减到0时,自然就可以认为整个python中不会再有变量引用这个对象,所以就可以把这个对象占据的存储空间释放出来了。
导致引用计数+1的情况

  • 对象被创建,例如:a = 1
  • 对象被引用,例如:b = a
  • 对象被作为参数,传入到一个函数中,例如:func(a)
  • 对象作为一个元素,存储在容器中,例如:list1 = [a]

导致引用计数-1的情况

  • 对象的别名被显式销毁,例如:del a
  • 对象的别名被赋予新的对象,例如:a = “hello word”
  • 一个对象离开它的作用域,例如f函数执行完毕时,f函数中的局部变量
  • 对象所在的容器被销毁,或从容器中删除对象

引用计数技术在每次引用创建和销毁时都要多做一些操作,这可能是一个小缺点,当创建和销毁很频繁时难免带来一些效率上的不足。但是其最大的好处就是实时性,其他语言中,垃圾回收可能只在一些固定的时间点上进行,比如当内存分配失败的时候进行垃圾回收,而引用计数可以动态的进行内存的管理。

循环引用

如果说效率只是一个不足的话,那么引用计数存在一些比较致命的软肋使得其一直不被接受为一种可以广泛运用的垃圾回收机制,便是对循环引用的处理。

在python中有一些类型比如tuple,list,dict等,其作为容器类型可以包含若那干个对象。循环引用就是一个对象直接或者间接的引用了自己本身,引用链行成了一个环。例如:

import sys
class Test():
  def __init__(self):
    pass
t = Test()
k = Test()
t._self = t
print sys.getrefcount(k) #sys.getrefcount函数用来查看一个对象有几个引用
print sys.getrefcount(t) 
#2
#3

getrefcount函数查看一个对象存在几个引用关系,一般状态下的普通变量如上面的k,返回值都是真实引用数加1。加1是因为调用getrefcount函数把k当参数传递时,需要先复制一份引用,然后把这个引用赋值给形式参数供函数运行。

从运行结果可以看出,Test类实例t添加了一个自己对自己的引用。del语句可以消除一个引用关系,但是对于存在循环引用的对象,引用计数仍然是1,得不到销毁,所以会造成内存泄漏。

要解决这个问题,python引入了其他的垃圾回收机制来弥补引用计数的缺陷:“标记-清除”,“分代回收”。

标记-清除

标记清除(Mark-Sweep)算法分为两个阶段:第一阶段是标记阶段,gc会把所有的活动对象打上标记,第二阶段是把没有打标记的非活动对象进行回收。 
对象之间通过引用(指针)连在一起,构成一个有向图,对象构成这个有向图的节点,而引用关系构成有向图的边。从根对象出发,沿着有向边遍历对象,可达的对象标记为活动对象,不可达对象就是要被清除的非活动对象。根对象就是全局变量、调用栈、寄存器。
在这里插入图片描述

在上图中,可以从程序变量之直接访问块1,并且可以间接访问块2和3。程序无法访问块4和5。第一步将标记块1,并记住块2和3以供稍后处理。第二步将标记块2,第三步将标记块3,但不标记块2,因为它已被标记。扫描阶段将忽略块1,2和3,因为它们已被标记,但会回收块4和5。
上面描述的垃圾回收的阶段,会暂停整个应用程序,等待标记清除结束后才会恢复应用程序的运行。
标记清除算法作为Python的辅助垃圾收集技术,主要处理的是一些容器对象,比如list、dict、tuple等,因为对于字符串、数值对象是不可能造成循环引用问题。Python使用一个双向链表将这些容器对象组织起来。不过,这种简单粗暴的标记清除算法也有明显的缺点:清除非活动的对象前它必须顺序扫描整个堆内存,哪怕只剩下小部分活动对象也要扫描所有对象。

分代回收

在循环引用对象的回收中,整个应用程序会被暂停,为了减少应用程序暂停的时间,引入了分代回收。
分代回收是建立在标记清除技术基础之上的,是一种以空间换时间的操作方式。分代回收是基于这样的一个统计事实:对于程序,存在一定比例的内存块的生存周期比较短;而剩下的内存块,生存周期会比较长,甚至会从程序开始一直持续到程序结束。生存期较短对象的比例通常在 80%~90% 之间,这种思想简单点说就是:对象存在时间越长,越可能不是垃圾,应该越少去收集。这样在执行标记-清除算法时可以有效减小遍历的对象数,从而提高垃圾回收的速度。
python将内存根据对象的存活时间划分为不同的集合,每个集合称为一个代,python将内存分为了3“代”,分别为年轻代(0代)、中年代(1代)、老年代(2代),对应的是3个链表,它们的垃圾收集频率随着对象的存活时间的增大而减小。新创建的对象会分配在年轻代,年轻代链表的总数达到上限时,python垃圾回收机制就会被触发,把那些可以回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,以此类推,老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内。

gc模块

对于循环引用的情况,一般的垃圾回收方式肯定是无效了,这时就需要显式地调用一些操作来保证垃圾的回收和内存不泄漏,用到了python内建的垃圾回收模块gc模块了。最常见的就是用gc.collect方法,通过这个模块,可以开关gc、调整垃圾回收的频率,输出调试信息。

import gc
gc.enable;gc.disable;gc.isenabled()
#开启gc,关闭gc,判断gc是否开启
gc.collect()
# 执行一次垃圾回收,不管gc是否开启都能进行回收。

gc模块不能处理的是两个循环引用的对象都实现了_ del _ 方法,那么将不会进行垃圾回收,因此尽量不要在类中实现自己的_ del _方法,否则发生循环引用后就会产生内存泄漏。

参考内容:
https://www.cnblogs.com/TM0831/p/10599716.html
https://zhuanlan.zhihu.com/p/83251959

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值