python语言默认采用的垃圾回收机制是引用计数法为主,标记清楚和分代回收为辅
原理:每个对象都维护一个ob_ref字段用来记录对象当前被引用的次数,每当新的引用指向该对象时,次数加1,当对象的引用消失时减1,一旦次数为0,会被立即回收,内存会被释放,缺点是不能解决‘’循环引用‘’的问题
导致引用计数+1的情况
1对象被创建,例如a=23
2对象被引用,例如b=a
3对象被作为参数,传入到一个函数中,例如func(a)
4对象作为一个元素,存储在容器中。如lists=[a,a]
导致引用计数-1的情况
1对象的别名被显式销毁,例如del a
2对象的别名被赋予新的对象,例如a=23,a=100
3一个对象离开它的作用域,例如f函数执行完毕时,func函数中的局部变量(全局变量不会)
4对象所在的容器(列表字典等)被销毁,或从容器中删除对象
import sys
class GarbageClass(object):
def __init__(self):
print("GarbageClass 创建,id 值为:%s"%str(hex(id(self))))
def __del__(self):
print("删除对象,id 值为:%s"%str(hex(id(self))))
#测试函数
def test():
while True:
#创建对象
gb = GarbageClass()
print("创建对象时的引用计数为:%d"%sys.getrefcount(gb)) #比正常次数大 1
#删除对象
del gb
test()
运行结果:
GarbageClass 创建,id 值为:0x3031a90
创建对象时的引用计数为:2
删除对象,id 值为:0x3031a90
查看引用次数用到sys模块下的 getrefcount(a)函数 a是对象名,但是比正常计数大1,因为调用函数的时候传入a,这会让a的引用计数+1
#引用计数
lists = []
#查看次数,上面是 1 次
import sys
sys.getrefcount(lists) #调用了一次,2 次
2 --真正只有 1 次
标记清除算法作为 Python 的辅助垃圾回收技术处理的主要是一些容器对象,比如 list、dict、tuple、实例对象等,而对于字符串、数值等这类不可变类型的对象是不可能造成循环引用问题的。Python 使用一个双向链表将这些容器对象组织起来。不过,这种简单粗暴的标记清除算法也有明显的缺点:清除非活动对象前,它必须顺序地扫描整个堆内存,哪怕只剩下小部分活动对象也要扫描堆内存中的所有对象。
分代回收是python将内存根据对象的存活时间分为不同的集合,分为三代,对应三个链表,它们的垃圾回收频率与对象的存活时间的增大而减小
常用函数模块
gc模块set_debug(flags) 设置gc的debug日志,一般设置为gc.DEBUG_LEAK
get_count() 获取当前自动执行垃圾回收的计数器,返回一个长度为 3 的列表。
disable()把python的gc自动回收垃圾机制进行关闭
import gc
class ClassA():
def __init__(self):
print('object born,id:%s'%str(hex(id(self))))
def __del__(self):
print('object del,id:%s'%str(hex(id(self))))
print(gc.get_count())
class_a = ClassA()
print(gc.get_count())
del class_a
print(gc.get_count())
结果
(634, 10, 0)
object born,id:0x35a2670
(635, 10, 0)
object del,id:0x35a2670
(634, 10, 0)
上述列表里(634, 10, 0)的634是代表距离上一次一代垃圾检查时,python解释器内存分配的
数目减去释放内存的数目(剩余内存分配的数目),注意是内存内存分配,而不是引用计数的增加;10是指距离上一次二代垃圾检查时,第一代垃圾检查的次数;同理,0是指距离上一次三代垃圾检查,第二代垃圾检查的次数。
对于垃圾回收机制的应用,通常有以下几种
(1)在实际项目中,尽量避免循环引用;–变量 1 调用 2、2 调用 1
(2)导入 gc 模块,最好启动 gc 模块的自动清理循环引用的对象机制;
(3)由于分代收集,所以把需要长期使用的变量集中管理,并尽快移到第二代以后,以减少 GC 检查时的消耗;
(4)gc 模块唯一处理不了的是循环引用的类中都有__del__()方法(继承:重写、重写后的__del__),因此项目中要避免定义__del__()方法,若一定要使用该方法,同时导致了循环引用,需要代码显式调用 gc.garbage 里面的对象的__del__()来打破僵局