Python采用的是引用计数机制为主,标记-清除和分代收集(隔代回收)两种机制为辅的策略
在Python中,如果一个对象的引用计数为0,Python虚拟机就会回收这个对象的内存
它核心就是一个结构体--PyObject
PyObject是每个对象必有的内容,其中ob_refcnt就是做为引用计数
有三种情况会触发垃圾回收:
1:调用gc.collect()
2:当gc模块的计数器达到阀值的时候
3:程序退出的时候
标记清除:
标记清除(Mark—Sweep)算法是一种基于追踪回收(tracing GC)技术实现的垃圾回收算法
它分为两个阶段:第一阶段是标记阶段,GC会把所有的活动对象打上标记,第二阶段是把那些没有标记的对象非活动对象进行回收
gc模块的自动垃圾回收机制:
必须要import gc模块,并且is_enable()=True才会启动自动垃圾回收
这个机制的主要作用就是发现并处理不可达的垃圾对象
垃圾回收=垃圾检查+垃圾回收
gc模块引用:
gc模块里面会有一个长度为3的列表的计数器,可以通过gc.get_count()获取
例如(488,3,0),其中488是指距离上一次一代垃圾检查,Python分配内存的数目减去释放内存的数目
项目中避免循环引用
引入gc模块,启动gc模块的自动清理循环引用的对象机制
由于分代收集,所以把需要长期使用的变量集中管理,并尽快移到二代以后,减少GC检查时的消耗
gc模块唯一处理不了的是循环引用的类都有__del__方法,所以项目中要避免定义__del__方法,如果一定要使用该方法,同时导致了循环引用,需要代码显式调用gc.garbage里面的对象的__del__来打破僵局
分代收集:
在Python中,采用分代收集的方法。把对象分为三代,一开始,对象在创建的时候,放在一代中,如果在一次一代的垃圾检查中,改对象存活下来,就会被放到二代中,同理在一次二代的垃圾检查中,该对象存活下来,就会被放到三代中
分代回收:
1、分代技术是一种典型的以空间换时间的技术,这也正是java里的关键技术,这种思想简单点说就是:对象存在时间越长,越可能不是垃圾,应该越少去收集
2、这样的思想,可以减少标记-清除机制所带来的额外操作,分代就是将回收对象分成数个代,每个代就是一个链表(集合),代进行标记-清除的时间与代内对象
3、存活时间成正比例关系
4、从上面代码可以看出python里一共有三代,每个代的threshold值表示该代最多容纳对象的个数,默认情况下,当0代超过700,或1,2代超过10,垃圾回收机制将触发
5、0代触发将清理所有三代,1代触发会清理1,2代,2代触发后只会清理自己
引用计数:
优点:简单实时,一旦没有引用,内存就直接释放了,不用像其他机制等到特定时机,实时性还带来一个好处:处理回收内存的时间分摊到了平时
缺点:维护引用计数消耗资源,会造成循环引用导致无法回收,造成内存泄露
对象被创建:
>>>import sys
>>>a = 123
>>>sys.getrefcount(a)
3
>>>b = 456
>>>sys.getrefcount(b)
2
这里实际上123这个对象并没有在内存中新建,因为
在Python启动解释器的时候会创建一个小整数池
对象被引用:
>>>import sys
>>>a = 456
>>>b = a
>>>c = b
>>>sys.getrefcount(a)
4
>>>sys.getrefcount(c)
4
每一次赋值操作都会增加数据的引用次数
要记住引用的变量a、b、c指向的是数据456,而不是变量本身
对象作为参数传递到函数中:
>>>import sys
>>>a =[1,2,3]
>>>sys.getrefcount(a)
2
>>>def f(x):
. . . x.append(4)
. . .
>>>f(a)
>>>a
[1,2,3,4]
>>>sys.getrefcount(a)
3
这里很明显看到被传递到函数中后,引用计数增加了1
对象作为元素储存到容器中:
>>>import sys
>>>a = 456
>>>sys.getrefcount(a)
2
>>>b = [a]
>>>sys.getrefcount(a)
3
>>>c = (a,)
>>>sys.getrefcount(a)
4
这里我们在创建对象之后,把a分别添加到了一个列表和一个元组中,引用计数都增加了