python采用引用计数为主,标记-清除(Mark-Sweep)和分代清除为辅的机制,其中标记-清除和分代回收主要是为了处理循环引用的难题。
引用计数算法
-
思想:
python中一切皆为对象,对象的核心就是一个结构体PyObject,里面包含引用计数器ob_refcnt,当对象增加一个引用时,ob_refcnt+1;当引用它的对象被删除时,ob_refcnt-1;当引用计数ob_refcnt==0时,对象的生命结束。 -
优点:
简单
实时性:当某个对象的引用计数为0时,内存马上就会被回收;不像其他需要等待特定的时机来进行垃圾回收。这样也带来了另一个好处:垃圾回收的时间也分摊到了平时。 -
缺点:
维护引用计数,消耗资源
致命的缺陷: 循环引用
class Student:
def __del__(self):
print('销毁对象{0}'.format(self))
s1 = Student()
s2 = Student()
del s2
print('over')
结果:
销毁对象<main.Student object at 0x000001DEB748B710>
over
销毁对象<main.Student object at 0x000001DEB73C3FD0>
首先销毁s2对象,程序调用结束后对象s1引用次数为0 程序自动销毁对象
# 循环引用的例子
list1 = []
list2 = []
list1.append(list2) # list1引用list2
list2.append(list1) # list2引用list1
list1和list2除了引用了对方,没有其他的引用,实际上,它们需要被回收,但是这种情况下采用引用计数机制,引用计数不为0,故不会被回收。面对这种循环引用,引用计数机制束手无策,故后面加入了面向循环引用的标记-清除和分代收集机制。
分代回收算法(以空间换时间)
基于“对象存活的时间越长,越可能不是垃圾,应该越少进行垃圾收集”的思想。
对象等级共分为0、1、2三代,每代对应一个链表,每代有一个代表各代最多允许的对象数量:threshold(默认情况下,generation 0 超过700,或generation 1、generation2超过10,会触发垃圾回收机制)
所有的新建对象都是0代,当一个对象经历过垃圾回收,依然存活,就会被归入下一代对象
generation 0触发,会将generation 0 、1、2依次链接起来再清理
generation 1触发,会将generation 1、 2依次链接起来再清理
generation 2触发,只会清理自己
什么时候会触发垃圾回收?
显式调用gc.collect()
上面说到的三代的计数器阈值threshold达到上限的时候
退出程序的时候
特殊情况,gc模块不能处理:
gc模块唯一处理不了的是循环引用的类带有_del_方法,所以项目中要避免定义_del_方法,如果一定要使用该方法,同时导致了循环引用,需要代码显式调用gc.garbage里面的对象的_del_来打破僵局。