Python的垃圾回收机制和其他的一些语言(如java)不同。Java使用跟踪收集器,从根集出发扫描各个对象,可以扫描到的对象不会被释放,其他对象应该被释放。
Python由于一些扩展模块(如C扩展模块)的工作方式,无法找到所有的根集对象,所以不能使用这种垃圾回收机制。
Python采用的方式也挺简单。因为python已经使用了引用计数方式(见
上文),引用计数不能解决的是循环引用问题,那回收机制只需要解决这个问题就可以了。
对于循环引用,只有容器对象才会存在该问题,python中的容器对象有list,tuple,dict,class,instances,python的内存管理模块会使用双向链表串联起这些对象,并为它们添加一个新的计数:gc_refs,然后使用以下步骤找出循环引用对象:
1. 设置双向链表中所有对象的gc_refs初始值为其引用计数值
2. 把每个对象中引用的对象的gc_refs值减1
3. 遍历双向链表,移除gc_refs大于1的对象,添加进新的集合中,这些对象的内存不能被释放
4. 遍历集合,在双向链表中找到集合中每个对象引用的对象,并移除,这些对象也不能被释放
5. 双向链表中剩余的对象就是无法访问到的对象,需要被释放
了解了原理之后,我们再来做试验。
我们需要导入python的gc包来操作内存垃圾回收。
-
>>> import gc
-
>>> gc.isenabled() #查看gc模块是否打开
-
True
默认情况下,gc自然是打开的。
如果我们的程序中能保证没有使用循环引用,那么为了提高效率可以调用gc.disable()关闭gc模块。
有两种情况会触发python的垃圾回收。
1. 用户显式调用gc.collect()
来看例子。
-
>>> import gc
-
>>> gc.collect()
-
0
-
>>> a = []
-
>>> a = 3
-
>>> gc.collect()
-
0
gc.collect()函数的返回值是释放对象的个数。执行a = 3的时候,a先前引用的对象“[]”就因为引用计数为0被释放了。gc模块没有释放任何对象,所以gc.collect()返回0
-
>>> a = []
-
>>> a.append(a)
-
>>> del(a)
-
>>> gc.collect()
-
1
这个是典型的循环引用,在这里引用计数失效了,gc.collect()返回1,说明本次垃圾回收回收了一个对象。
2. Python会记录分配对象和释放对象的次数。当两者的差值大于阈值时,进行垃圾回收。
同时,python还采用分代(generation)回收策略。
一般情况下,对象存在时间越久就越不容易变成垃圾。基于这点假设,python将所有的对象分成三代(0代、1代、2代)。新建对象都是0代对象。如果对象经历了一次垃圾回收后依然存活,就变成下一代对象。垃圾回收时,会扫描所有的0代对象,0代对象扫描次数到达一个
阈值后,扫描1代对象,1代对象扫描次数到达另一个
阈值后,扫描2代对象,如此循环。
以上3个(红色字体的)阈值可以由函数gc.set_threshold(threshold0[, threshold1[, threshold2]])来指定。
使用gc.get_threshold()函数可以获取当前的配置:
-
>>> gc.get_threshold()
-
(700, 10, 10)
默认情况下,gc的配置为700,10,10,也就是说,当分配对象次数比释放对象次数大700的时候,开始启动垃圾回收,每10次0代对象回收后会进行1次1代回收,每10次1代回收后会进行1次2代回收。
阅读(3443) | 评论(0) | 转发(0) |